-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Add the FDv2 data system and expose it through configuration #310
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
Draft
kinyoklion
wants to merge
4
commits into
main
Choose a base branch
from
rlamb/sdk-2186/fdv2-data-system
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
6225462
feat: Add the FDv2 data system and expose it through configuration
kinyoklion 6c00880
feat: load the FDv2 cache through the pipeline and run offline as a d…
kinyoklion 7f96be5
refactor: reset the FDv2 basis at identify, not in the data source fa…
kinyoklion 03fa4eb
test: verify the FDv2 mode override is selected at the resolution layer
kinyoklion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
packages/common_client/lib/src/config/data_system_config.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import '../data_sources/fdv2/mode_definition.dart'; | ||
|
|
||
| // Maintainer note (not public API): ConnectionModeId is a sealed | ||
| // hierarchy rather than an enum so a custom-mode variant can be added | ||
| // later without changing this surface. The planned extension is a custom | ||
| // variant constructed as `ConnectionModeId.custom('my-mode')`: | ||
| // | ||
| // factory ConnectionModeId.custom(String name) = _CustomConnectionMode; | ||
| // final class _CustomConnectionMode extends ConnectionModeId { | ||
| // final String name; | ||
| // const _CustomConnectionMode(this.name); | ||
| // // value equality on name so it works as an override-map key | ||
| // } | ||
| // | ||
| // A custom mode is a distinct type from a built-in, so the two share no | ||
| // namespace: a custom id never equals a built-in id (even with the same | ||
| // name), and so cannot collide with a current or future built-in. The | ||
| // type is the namespace -- no name prefix is needed. This holds only | ||
| // while custom modes stay typed; if one is ever reduced to a bare string | ||
| // (logs, persistence) that reintroduces a shared string space where a | ||
| // prefix would matter again. | ||
| // | ||
| // Equality split: the built-in values are const singletons relying on | ||
| // canonical-instance identity, which lets a connectionModes map of only | ||
| // built-in keys be a const map. A runtime-constructed custom variant must | ||
| // carry value equality, so an override map holding a custom key would be | ||
| // non-const. The built-in variant therefore must not override | ||
| // `==`/`hashCode`. | ||
|
|
||
| /// Identifies a built-in connection mode whose data-source pipeline can be | ||
| /// overridden through [DataSystemConfig.connectionModes]: [streaming], | ||
| /// [polling], [background], or [offline]. | ||
| sealed class ConnectionModeId { | ||
| const ConnectionModeId(); | ||
|
|
||
| /// The built-in streaming mode. | ||
| static const ConnectionModeId streaming = _BuiltInConnectionMode('streaming'); | ||
|
|
||
| /// The built-in polling mode. | ||
| static const ConnectionModeId polling = _BuiltInConnectionMode('polling'); | ||
|
|
||
| /// The built-in background mode. | ||
| static const ConnectionModeId background = | ||
| _BuiltInConnectionMode('background'); | ||
|
|
||
| /// The built-in offline mode. Its pipeline loads cached flags and runs | ||
| /// no synchronizer, so overriding it customizes how the SDK behaves | ||
| /// while offline (for example, the cache initializer it uses). | ||
| static const ConnectionModeId offline = _BuiltInConnectionMode('offline'); | ||
| } | ||
|
|
||
| final class _BuiltInConnectionMode extends ConnectionModeId { | ||
| final String name; | ||
|
|
||
| const _BuiltInConnectionMode(this.name); | ||
|
|
||
| @override | ||
| String toString() => 'ConnectionModeId.$name'; | ||
| } | ||
|
|
||
| /// Configuration for the FDv2 data system. | ||
| /// | ||
| /// Providing a [DataSystemConfig] (even an empty one) opts the SDK into | ||
| /// the FDv2 data acquisition protocol. When absent the SDK uses the | ||
| /// FDv1 data sources. | ||
| /// | ||
| /// This feature is not stable, and not subject to any backwards | ||
| /// compatibility guarantees or semantic versioning. It is in early | ||
| /// access. If you want access to this feature please join the EAP. | ||
| final class DataSystemConfig { | ||
| /// Overrides for built-in connection modes. A definition given here | ||
| /// replaces the built-in pipeline for that mode; modes not present keep | ||
| /// their built-in definition. | ||
| final Map<ConnectionModeId, ModeDefinition> connectionModes; | ||
|
|
||
| const DataSystemConfig({ | ||
| this.connectionModes = const {}, | ||
| }); | ||
| } | ||
85 changes: 85 additions & 0 deletions
85
packages/common_client/lib/src/data_sources/data_manager.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart' | ||
| show LDContext; | ||
|
|
||
| import '../flag_manager/flag_manager.dart'; | ||
| import 'data_source_manager.dart'; | ||
|
|
||
| /// Owns the data-acquisition strategy for an identify: how the cache is | ||
| /// loaded and when the identify resolves. The FDv1 and FDv2 protocols | ||
| /// diverge here, so each has its own implementation; everything else | ||
| /// (connection lifecycle, mode switching, event routing) is shared in the | ||
| /// [DataSourceManager] that both delegate to. | ||
| abstract interface class DataManager { | ||
| /// Brings the SDK to a usable state for [context], resolving when the | ||
| /// manager's data-availability strategy is satisfied. | ||
| /// | ||
| /// When [waitForNetworkResults] is true the returned future resolves | ||
| /// only once network (or otherwise fresh) data has arrived; otherwise it | ||
| /// may resolve as soon as cached data is available. | ||
| Future<void> identify(LDContext context, | ||
| {required bool waitForNetworkResults}); | ||
| } | ||
|
|
||
| /// FDv1 data manager. | ||
| /// | ||
| /// The cache is loaded imperatively at identify time via | ||
| /// [FlagManager.loadCached]. A cache hit resolves identify immediately | ||
| /// unless the caller is waiting for network results; either way the | ||
| /// network connection is started so live data follows. | ||
| final class FDv1DataManager implements DataManager { | ||
| final DataSourceManager _dataSourceManager; | ||
| final FlagManager _flagManager; | ||
|
|
||
| FDv1DataManager(this._dataSourceManager, this._flagManager); | ||
|
|
||
| @override | ||
| Future<void> identify(LDContext context, | ||
| {required bool waitForNetworkResults}) async { | ||
| final completer = Completer<void>(); | ||
| final loadedFromCache = await _flagManager.loadCached(context); | ||
| _dataSourceManager.identify(context, completer); | ||
| if (loadedFromCache && !waitForNetworkResults) { | ||
| return; | ||
| } | ||
| return completer.future; | ||
| } | ||
| } | ||
|
|
||
| /// FDv2 data manager. | ||
| /// | ||
| /// The cache is not loaded at identify time; the data source pipeline's | ||
| /// cache initializer loads it as the first tier. Identify resolves on the | ||
| /// first delivered payload, or -- when waiting for network results -- only | ||
| /// on basis (network or terminal) data, so a cache load alone does not | ||
| /// satisfy a wait-for-network identify. | ||
| /// | ||
| /// Identify is also where a held basis is discarded: a selector points at | ||
| /// one context's data, so on a context change [resetBasis] is invoked | ||
| /// before connecting. This is keyed on the context's canonical key and | ||
| /// driven here rather than inferred from the context instance inside the | ||
| /// data source factory, so it holds regardless of which connection mode is | ||
| /// active when the context changes (including offline). | ||
| final class FDv2DataManager implements DataManager { | ||
| final DataSourceManager _dataSourceManager; | ||
| final void Function() _resetBasis; | ||
|
|
||
| String? _lastContextKey; | ||
|
|
||
| FDv2DataManager(this._dataSourceManager, this._resetBasis); | ||
|
|
||
| @override | ||
| Future<void> identify(LDContext context, | ||
| {required bool waitForNetworkResults}) { | ||
| final key = context.canonicalKey; | ||
| if (key != _lastContextKey) { | ||
| _lastContextKey = key; | ||
| _resetBasis(); | ||
| } | ||
| final completer = Completer<void>(); | ||
| _dataSourceManager.identify(context, completer, | ||
| requireFreshData: waitForNetworkResults); | ||
| return completer.future; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.