Skip to content
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

Merged
merged 10 commits into from
Mar 31, 2020

Conversation

zashraf1985
Copy link
Contributor

@zashraf1985 zashraf1985 commented Mar 23, 2020

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

@coveralls
Copy link

coveralls commented Mar 23, 2020

Coverage Status

Coverage remained the same at 97.222% when pulling b0fd21f on zeeshan/react-native-datafile-manager into 24fe5d6 on master.

@zashraf1985 zashraf1985 marked this pull request as ready for review March 23, 2020 06:39
@zashraf1985 zashraf1985 requested a review from a team as a code owner March 23, 2020 06:39
Comment on lines 109 to 119
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()
})
}
})
Copy link
Contributor

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 be sdkKey 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 that start and stop should be controlling all async I/O requests and timers. Prior to start 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.

Comment on lines 332 to 346

// 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()
}
Copy link
Contributor

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'
Copy link
Contributor

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.

Copy link
Contributor

@mjc1283 mjc1283 left a 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,
Copy link
Contributor

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:

Suggested change
cache,
cache = new NoOpKeyValueCache()

@@ -34,6 +35,24 @@ function isSuccessStatusCode(statusCode: number): boolean {
return statusCode >= 200 && statusCode < 400
}

class NoOpKeyValueCache implements PersistentKeyValueCache {
Copy link
Contributor

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(...) {
   ...
  },
  ...
};

Comment on lines -109 to -112
expect(updateFn).toBeCalledTimes(1)
expect(updateFn).toBeCalledWith({
datafile: { foo: 'bar' }
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this deleted?

Copy link
Contributor

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) {
Copy link
Contributor

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).

Copy link
Contributor

@mjc1283 mjc1283 left a 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()
Copy link
Contributor

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.

Copy link
Contributor Author

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')
Copy link
Contributor

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.

@mjc1283 mjc1283 merged commit 645ea8a into master Mar 31, 2020
@mjc1283 mjc1283 deleted the zeeshan/react-native-datafile-manager branch March 31, 2020 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants