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

refactor: new ConfigurationStore #256

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

rasendubi
Copy link
Collaborator

WIP changes

  • Make Configuration a passive/immutable piece of data that encapsulates flags/bandits/etc
  • Split current configuration store into two components:
    • ConfigurationStore as a central piece that just tracks the currently active configuration (in memory). The rest of components integrate with ConfigurationStore and communicate via getting/setting/listening for Configuration (see picture)
    • Persistence component with two features: loading initial configuration and persisting subsequent configurations (in background)

TODO:

  • Integrate persistence (race initial load in init, store configurations on change)
  • cleanup
  • updates to client and node sdks

image

@rasendubi rasendubi requested a review from typotter March 20, 2025 16:09
Copy link
Collaborator

@typotter typotter left a comment

Choose a reason for hiding this comment

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

Love seeing more code deleted than added, yet new features developing from it.

@@ -0,0 +1,5 @@
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

🧹

format: configResponse.format,
};

if (
Copy link
Collaborator

Choose a reason for hiding this comment

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

We check to see if the set of bandit models we've already loaded satisfies the models referenced by the latest flag config. Limits calls to fetch the bandits to once daily (bandit model recomputation cycle)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oh, good catch

}

/** @internal */
type ConfigurationWire = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

heads up, IConfigurationWire is already defined here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. I tried using it but ran into it having readonly fields, so I couldn't construct it like this:

    const wire: ConfigurationWire = {
      version: 1,
    };
    if (this.flags) {
      wire.config = {
        ...this.flags,
        response: JSON.stringify(this.flags.response),
      };
    }
    // ...

It's also quite a bit more convoluted with braided strings and class implementation, etc.

So my idea is to have the simplest definition here, just for sanity check—it's not exported and is not used for anything but Configuration.toString()/Configuration.fromString(). (The design idea is that it's just a string representation of Configuration.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why delete?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess the BaseEppoClient would actually provide this functionality.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, ConfigurationRequestor is basically doing the same thing now with the new fetchConfiguration() method (when paired with Configuration.toString())


/** @internal
*
* Returns flag configuration for the given flag key. Obfuscation is
Copy link
Collaborator

Choose a reason for hiding this comment

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

💪

@@ -14,7 +15,7 @@ import {
Allocation,
Split,
VariationType,
ConfigDetails,
Copy link
Collaborator

Choose a reason for hiding this comment

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

🎊

);
const configFormat = flagsConfig?.response.format;
const obfuscated = configFormat !== FormatEnum.SERVER;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should isObfuscated be a method on Configuration so we're not leaking the FormatEnum details?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

isObfuscated() is oversimplification in a sense that Configuration contains multiple pieces (flags, bandits, precomputed), where each could be obfuscated, not obfuscated, or simply missing. So a single isObfuscated() method doesn't cut it.

It also spreads this oversimplified model in programmers' brains, so we're more likely to miss some edge cases. I would rather have us working with raw data instead of half-baked abstractions. It may not be as pretty (initially) but at least it's obvious what's going on

re leaking details: my philosophy is that SDKs are in the know. Evaluation is highly dependent on flags response type, so there's no sense hiding it. When talking about leaking details, the only meaningful distinction for me is user–SDK. Configuration is exposing full server responses but is marking them @internal for this reason

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

re FormatEnum specifically: it's currently mixing formats from two independent and unrelated configuration pieces (flags and precomputed), that actually have different rules for determining obfuscation

Next refactoring step is splitting it into FlagsConfigurationFormat and PrecomputedConfigurationFormat. And then we can have a proper isObfuscated(FlagsConfigurationFormat): boolean helper to encode this piece of knowledge

Longer-term still, we shall add isObfuscated field to flags configuration

return undefined;
}
return {
response,
Copy link
Collaborator

Choose a reason for hiding this comment

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

great steps towards passing and checking the eTag in this SDK

Comment on lines +1242 to +1243
obfuscated:
this.getConfiguration()?.getFlagsConfiguration()?.response.format === FormatEnum.CLIENT,
Copy link
Collaborator

Choose a reason for hiding this comment

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

obfuscated computation should move somewhere centralized

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.

2 participants