Best Way to Cache API Response?

For performance and reduced API calls, I want to cache API call responses for a period of time. There are two potentially separate issues here:
(1) Caching within a single execution of the app; and
(2) Caching across different executions of the app.

Regarding (1), my app makes repeated API calls over a period of time, by recursively using the runIn() method call. I suspect this runIn() call extends the duration of a single "execution", so that any state variables are only written to storage when no more runIn() calls are made. If that's the case, my understanding is that a state variable will not function as a cache during this time period.

(assuming that state works the same way as documented for smartthings: Storing Data With State — SmartThings Classic Developer Documentation)

So, my suspicion is that I need to do something like this (ignoring the issue of clearing the cache after a period of time). This uses a local variable for the cache during a single execution of the app, but initializes the cache from the state upon the start of a new execution.

cache = null   // global variable across all methods in app

def initialize() {
    if (state.cache) cache = state.cache
}

def myMethod() {
    if (cache) return cache
    else {
        def response = makeApiCall()
        cache = response
        state.cache = cache
        return response
    }
}

Is this the best way to implement a cache? Would atomicState be better or worse?

UPDATE: I'm realizing my haste in writing the question led to wrongly initializing the cache within the initialize() function, which is only called upon initial install (or often update) of the app, not upon a new execution of the app.... So, I'll need to think some more....

state is read when the app starts and written out when the app is done, but that doesn't mean it's not available in the meantime. The only issue there is that if another instance of the app starts running during that time, they'll both read that "old" state value (the first instance will not yet have written its out) and only the last app to write out will "win." In any case, as it relates to this, you don't need to worry about "caching" within a single execution of the app; that already describes state. Using atomicState is likely to help with the "race condition" just described (in that apps will write out atomicState soon as they can, not when they're done), but I'm not sure that's actually a concern here based on your description.

This just schedules another instance ("execution") at the scheduled time in the future. (Check "Scheduled Jobs" on the gear/status page of the app: anything there will cause another instance of the app to run, as will the matching of any device/event subscription.) I assume this addresses the other aspect of this question.

Finally, this:

...won't work for Groovy reasons (top-level code in a script gets shoved into a method behind the scenes [Java compatibility and whatnot], and then that variable is only accessible from within that method, which other methods can't see, just like any variable you declare within a method). Using the @Field annotation would work around this particular problem and I've seen some people use these as a sort of cache/storage, but I don't think I've ever seen code from staff that uses this mechanism for doing so (nor have I seen its expected behavior across different instances documented), and I'd be hesitant to recommend it for that reason.

2 Likes

Is state cleared when initialize() or updated() is called, or does it persist?

They'll persist--nothing clears state or removes any state keys unless you do so yourself. (Also, a tip: initialize() isn't even a special/standard method in apps, just something you'll see in lots of examples since there may be common code you want to do both on install() and updated().)

1 Like

This topic was automatically closed 12 hours after the last reply. New replies are no longer allowed.