Skip to content

feat(utils): Add PollingConfigCache and ClientCache #96

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

Closed
wants to merge 16 commits into from

Conversation

spencerwilson-optimizely
Copy link
Contributor

@spencerwilson-optimizely spencerwilson-optimizely commented May 14, 2018

Summary

  • Add public types PollingConfigCache and ClientCache, both of which are subclasses of a [currently] private type AsyncCache
  • Move the definition of the createInstance export to lib/optimizely/index.js, in order to resolve a circular-import issue: since ClientCache refers to it, but the entry point modules in turn refer to ClientCache.

How to read this: start with AsyncCache, then move on to the other types. It is a bit like the type exported by the popular async-cache package, but with some differences. This implementation:

  • Is a class, so can be extended, having methods overridden (like seed, whose semantics are quite different for ClientCache than they are for PollingConfigCache).
  • Lacks any notions of entry expiration or eviction, since our use case is a small dataset (Full Stack configs). See YAGNI.
  • Subclasses may spur entry refreshes at their discretion by calling __execRefresh(key).
  • Supports other JavaScript host environments, like a browser (async-cache depends on process.nextTick).

This AsyncCache is totally generic, so could/should live in another package.

Both the Node.js and browser builds (which reminds me, this PR must not be merged until babel transpilation is introduced) of PollingConfigCache have a functional default means of polling the CDN: request-promise-native and window.fetch, respectively. request-promise-native and its peer dependency "request" are optionalDependencies.

Test plan

Currently some very basic unit tests. Some manual tests have validated basic getAsync use of both types, at-most-one-in-flight behavior, and support for tracking headers so PollingConfigCache can get 304 responses from the CDN.

@spencerwilson-optimizely spencerwilson-optimizely changed the title feat(utils): Add ConfigCache and ClientCache feat(utils): Add PollingConfigCache and ClientCache May 14, 2018
Copy link
Contributor

@mikeproeng37 mikeproeng37 left a comment

Choose a reason for hiding this comment

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

Initial round of feedback. I need to spend more time looking at the cache implementations and maybe play around with them. Will get back to it later this week.

var Optimizely = require('./optimizely');

var MODULE_NAME = 'INDEX';
var { PollingConfigCache } = require('./utils/config_cache');
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is supposed to be an overridable component, I think it should be in the plugins directory as with the rest of the overridable components. Also, I was thinking we can even make this it's own package and later provide an entry point in here that allows users to not include that package (not that we can't do it without making it a separate package). Just drawing inspiration from other open source projects that split up in multiple packages like React and Apollo


var MODULE_NAME = 'INDEX';
var { PollingConfigCache } = require('./utils/config_cache');
var { ClientCache } = require('./utils/client_cache');

/**
* Entry point into the Optimizely Node testing SDK
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is no longer valid :)

return {
body: await response.text(),
headers: Array.from(response.headers.entries()).reduce((acc, [k, v]) => {
acc[k] = v;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use proper variable names

]
],
"optionalDependencies": {
"request": "^2.86.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

@tylerbrandt later introduced this PR that removes this dependency because of vulnerabilities: #98 can we accomplish the same here using just http and https? Perhaps we can even have a promisified wrapper around them

}

config = fns.assignIn({
clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE,
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to vary by entrypoint because it lets the backend know whether it is a node or browser client.

* Fulfills ASAP in accord with the result of `__onGetAsync` or the given override.
* Rejects on refresh error.
*/
async getAsync(key, override) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Document override as an optional param. Also, can name it more explicitly, like refreshDirectiveOverride.

/**
* A ConfigCache that syncs by polling the CDN.
*
* Parametrized by which requester to use as a default, since Node.js and browser
Copy link
Contributor

Choose a reason for hiding this comment

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

Parameterized

/**
* The function that PollingConfigCache should use by default to update a config.
*/
async function browserRequester(url, headers) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe move this into utils or even plugins (with the idea that we'll let you configure your own requester in a later version)? Let's try not to bloat the entry point file

this.__execRefresh(key);
return this.get(key);

case enums.refreshDirectives.YES_AWAIT:
Copy link
Contributor

Choose a reason for hiding this comment

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

We do need to handle the default case in case of user error and probably default to one of the above and log a warning about it.

* within this.configCache. _May_ involve losing per-instance state. In particular,
* forcedVariations and notificationListeners are lost. TODO: Fix this.
*/
async __refresh(configKey, { client: currentClient, config: currentConfig } = {}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please document params

@mikeproeng37
Copy link
Contributor

This is stale, gonna close for now.

@mikeproeng37 mikeproeng37 deleted the config-cache branch February 21, 2019 23:49
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.

2 participants