-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: React native datafile manager that supports Datafile caching. #430
Conversation
this.hasCachedDatafile() | ||
.then((hasCachedDatafile: Boolean) => { | ||
if (hasCachedDatafile) { | ||
logger.debug('Found datafile in cache') | ||
this.getCachedDatafile().then((datafile: any) => { | ||
logger.debug('Using datafile from cache') | ||
this.currentDatafile = datafile | ||
this.resolveReadyPromise() | ||
}) | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments on this area:
- The presence or absence of
datafile
should not determine whether caching is in effect; rather it should besdkKey
driving the usage of cache. - We should use the
start
method to kick off the cache request. Although it's not very consequential given how datafile manager is used within optimizely-sdk, to maintan some internal coherence, I was thinking thatstart
andstop
should be controlling all async I/O requests and timers. Prior tostart
being called, the datafile manager instance is inert: no timers or I/O requests. - Network datafile should always be set after cache datafile, so cache requests need to be coordinated with network requests to avoid race conditions. I would try some refactoring so that the initialization involving both cache and network requests are easier to follow.
|
||
// Override in the child class to add cache implementation | ||
protected addToCache(datafile: any): Promise<void> { | ||
return Promise.resolve() | ||
} | ||
|
||
// Override in the child class to add cache implementation | ||
protected hasCachedDatafile(): Promise<Boolean> { | ||
return Promise.resolve(false) | ||
} | ||
|
||
// Override in the child class to add cache implementation | ||
protected getCachedDatafile(): Promise<any> { | ||
return Promise.resolve() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than using inheritance and overrides, I suggest we allow passing in a cache object as part of DatafileManagerConfig
when constructing HttpPollingDatafileManager
. If none is passed in, we can skip the cache reading/writing, or fill in a no-op cache implementation as a default, like you did here.
React Native datafile manager can provide its own default cache object using the getConfigDefaults
method override.
|
||
export * from './datafileManager' | ||
export { default as HttpPollingDatafileManager } from './reactNativeDatafileManager' | ||
export { default as StaticDatafileManager } from './staticDatafileManager' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Static datafile manager is not used and actually we should remove it from the codebase (you don't have to do that now). But we should not export it from here, nor create a react native version of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, nice work @zashraf1985. We should add some more unit tests covering the new caching behavior.
@@ -91,9 +112,11 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana | |||
sdkKey, | |||
updateInterval = DEFAULT_UPDATE_INTERVAL, | |||
urlTemplate = DEFAULT_URL_TEMPLATE, | |||
cache, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: suggest add default here like the other config values:
cache, | |
cache = new NoOpKeyValueCache() |
@@ -34,6 +35,24 @@ function isSuccessStatusCode(statusCode: number): boolean { | |||
return statusCode >= 200 && statusCode < 400 | |||
} | |||
|
|||
class NoOpKeyValueCache implements PersistentKeyValueCache { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be a single shared object rather than a class, because it has no instance state:
const noOpKeyValueCache: PersistentKeyValueCache {
get(...) {
...
},
...
};
expect(updateFn).toBeCalledTimes(1) | ||
expect(updateFn).toBeCalledWith({ | ||
datafile: { foo: 'bar' } | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I see why this behavior changed. We are only emitting update events after ready is resolved. Deleting this is OK.
setDatafileFromCacheIfAvailable(): void { | ||
this.cache.get(this.cacheKey) | ||
.then(datafile => { | ||
if (!this.isReadyPromiseSettled && datafile) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The datafile manager can be stopped at any time. When it's stopped, we want to cancel all pending timers, abort requests, and cut short any already-scheduled promise reactions. So let's add a check for this.isStarted
in here and return early if it's false (just like other promise reactions in this class).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just one comment
await advanceTimersByTime(50) | ||
expect(manager.get()).toEqual({name: 'keyThatExists'}) | ||
expect(updateFn).toBeCalledTimes(0) | ||
updateFn.mockReset() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does calling mockReset
matter? updateFn
is just thrown away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right. I naively copied without thinking. The places where i copied it from was verifying it twice hence resetting after first use. Pifalls of copy/pasting :)
@@ -197,6 +226,8 @@ export default abstract class HttpPollingDatafileManager implements DatafileMana | |||
if (datafile !== null) { | |||
logger.info('Updating datafile from response') | |||
this.currentDatafile = datafile | |||
logger.debug('Adding Datafile to Cache') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this log message. It will be confusing for users to see this with the no-op cache.
Summary
This overrides existing
HttpPollingDatafileManager
to support Datafile Caching for React Native. Browser and Node implementations can also override the same methods to support datafile caching.Test plan
Unit Tests Pending