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: [add syncInit method] (FF-2431) #81

Merged
merged 12 commits into from
Jun 21, 2024
Merged

Conversation

leoromanovsky
Copy link
Member

@leoromanovsky leoromanovsky commented Jun 19, 2024

Fixes: #issue

Motivation and Context

Users need to be able to bootstrap the JS SDK from configuration obtained from their server, or another external process. The format of this configuration might or might not be obfuscated.

Furthermore users need to be able to instantiate the SDK synchronously.

Description

  • adds a new public function syncInit - it accepts a map of flags in both obfuscated and not-obfuscated formats. pairs with the setIsObfuscated parameter to make the SDK understand the incoming data.
  • Augments the README with instructions for how to use this method.

How has this been tested?

New unit tests to validate behavior of providing obfuscated and not-obfuscated configuration.

@leoromanovsky leoromanovsky force-pushed the lr/ff-2431/sync-init branch from 2e5636d to 8c18a65 Compare June 21, 2024 02:59
@leoromanovsky leoromanovsky force-pushed the lr/ff-2431/sync-init branch from 8c18a65 to 2e12c7b Compare June 21, 2024 03:43
@leoromanovsky leoromanovsky marked this pull request as ready for review June 21, 2024 03:46
@@ -16,7 +16,7 @@ getIntegerAssignment(flagKey: string, subjectKey: string, subjectAttributes: Rec
| --- | --- | --- |
| flagKey | string | |
| subjectKey | string | |
| subjectAttributes | Record<string, any> | |
| subjectAttributes | Record<string, AttributeType> | |
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't get why this is changing - I'm assuming someone else is checking in doc changes without this?

Comment on lines 367 to 394
describe('sync init', () => {
it('initializes with flags in obfuscated mode', () => {
const client = initSync({
isObfuscated: true,
flagsConfiguration: {
[obfuscatedFlagKey]: mockObfuscatedUfcFlagConfig,
},
});

expect(client.getStringAssignment(flagKey, 'subject-10', {}, 'default-value')).toEqual(
'variant-1',
);
});

it('initializes with flags in not-obfuscated mode', () => {
const client = initSync({
isObfuscated: false,
flagsConfiguration: {
[flagKey]: mockNotObfuscatedFlagConfig,
},
});

expect(client.getStringAssignment(flagKey, 'subject-10', {}, 'default-value')).toEqual(
'variant-1',
);
});
});

Copy link
Member Author

Choose a reason for hiding this comment

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

new tests for the functionality being added in this PR

@@ -47,7 +55,64 @@ const obfuscatedFlagKey = md5Hash(flagKey);
const allocationKey = 'traffic-split';
const obfuscatedAllocationKey = base64Encode(allocationKey);

const mockUfcFlagConfig: Flag = {
Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed this to be called obfuscated for clarity

src/index.ts Outdated
Comment on lines 113 to 123
export interface IClientConfigSync {
flagsConfiguration: Record<string, Flag>;

assignmentLogger?: IAssignmentLogger;

isObfuscated?: boolean;

throwOnFailedInitialization?: boolean;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

These are the possible configuration options for calling the initSync method - note that no SDK key is provided as it will be operating in off-line mode.

Assuming that on the server the user stringified the configurations to a JSON string, they will need to parse it back into the object using the types we export in the SDK.

src/index.ts Outdated
}

// There is no SDK key in the offline context.
const storageKeySuffix = buildStorageKeySuffix('no_api_key');
Copy link
Member Author

Choose a reason for hiding this comment

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

Got another idea for handling this?

Copy link
Member

Choose a reason for hiding this comment

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

just use a random string instead, I think that would be cleaner

Copy link
Member Author

Choose a reason for hiding this comment

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

The only thing I don't love about this:

  • generate a random string in JS seems like so much code [...Array(8)].map(() => Math.random().toString(36)[2]).join(''); but that's fine
  • if we move to having a persistent store i don't want to generate a different random string each initialization which would make the store useless. perhaps a different static string like offline is more clear?

Copy link
Member

Choose a reason for hiding this comment

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

yeah I think either should be fine, as long as the intention is clear. could be as simple as extracting to a function like generateStorageCacheKeySuffixForOffline or something like that

Comment on lines +254 to +265
// As this is a synchronous initialization,
// we are unable to call the async `init` method on the assignment cache
// which loads the assignment cache from the browser's storage.
// Therefore there is no purpose trying to use a persistent assignment cache.
const assignmentCache = assignmentCacheFactory({
storageKeySuffix,
forceMemoryOnly: true,
});
EppoJSClient.instance.useCustomAssignmentCache(assignmentCache);
Copy link
Member Author

Choose a reason for hiding this comment

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

There is still value to have an in-memory cache as it will remove duplicates for the duration of the session. Perhaps we can think creatively how to support this.

Comment on lines +292 to +296
// Default to obfuscated mode when requesting configuration from the server.
instance.setIsObfuscated(true);
Copy link
Member Author

Choose a reason for hiding this comment

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

Making it clear here.

Copy link
Member

@felipecsl felipecsl left a comment

Choose a reason for hiding this comment

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

Main feedback is around the assignment cache/config cache usage, otherwise looks good to me

// configuration from your server SDK
const configurationJsonString: string = getConfigurationFromServer();
// The configuration will be not-obfuscated from your server SDK. If you have obfuscated flag values, you can use the `ObfuscatedFlag` type.
const flagsConfiguration: Record<string, Flag | ObfuscatedFlag> = JSON.parse(configurationJsonString);
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think i would omit the types for brevity since they are inferred anyways

docs/js-client-sdk.chromestorageengine._constructor_.md Outdated Show resolved Hide resolved
src/index.ts Outdated Show resolved Hide resolved
const simpleCache = new SimpleAssignmentCache();
if (forceMemoryOnly) {
Copy link
Member

Choose a reason for hiding this comment

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

instead of doing this, which makes this method more complex (unecessarily, I think), i'd just instantiate SimpleAssignmentCache directly from syncInit

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea - I don't have strong feelings about it either way. On the one hand this method becomes the central way to instantiate caches without the caller needing to know anything about the SimpleAssignmentCache besides saying they want an inMemory.

Copy link
Member

Choose a reason for hiding this comment

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

i feel like it's an unnecessary abstraction that adds complexity. not gonna block on it, but it's my personal preference ;)

src/index.ts Outdated
}

// There is no SDK key in the offline context.
const storageKeySuffix = buildStorageKeySuffix('no_api_key');
Copy link
Member

Choose a reason for hiding this comment

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

just use a random string instead, I think that would be cleaner

src/index.ts Outdated Show resolved Hide resolved
@leoromanovsky leoromanovsky merged commit c8f84db into main Jun 21, 2024
2 checks passed
@leoromanovsky leoromanovsky deleted the lr/ff-2431/sync-init branch June 21, 2024 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants