Skip to content

Commit f241f3b

Browse files
authoredApr 2, 2025··
chore: update readme (#53)
## This PR - update readme - add install and usage to the readme ### Related Issues - #43
1 parent b30b0fe commit f241f3b

File tree

1 file changed

+421
-15
lines changed

1 file changed

+421
-15
lines changed
 

‎README.md

+421-15
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,79 @@ Dart language version: [3.7.2](https://dart.dev/get-dart/archive)
5858
5959
### Install
6060

61-
TBD
61+
```yaml
62+
dependencies:
63+
openfeature_dart_server_sdk: ^0.0.6
64+
```
65+
66+
### Then run:
67+
68+
```
69+
dart pub get
70+
```
71+
72+
### Usage
73+
74+
```dart
75+
import 'dart:async';
76+
import 'package:openfeature_dart_server_sdk/client.dart';
77+
import 'package:openfeature_dart_server_sdk/open_feature_api.dart';
78+
import 'package:openfeature_dart_server_sdk/feature_provider.dart';
79+
import 'package:openfeature_dart_server_sdk/evaluation_context.dart';
80+
import 'package:openfeature_dart_server_sdk/hooks.dart';
81+
82+
void main() async {
83+
// Register your feature flag provider
84+
final api = OpenFeatureAPI();
85+
api.setProvider(InMemoryProvider({
86+
'new-feature': true,
87+
'welcome-message': 'Hello, OpenFeature!'
88+
}));
89+
90+
// Create a client
91+
final client = FeatureClient(
92+
metadata: ClientMetadata(name: 'my-app'),
93+
hookManager: HookManager(),
94+
defaultContext: EvaluationContext(attributes: {}),
95+
);
96+
97+
// Evaluate your feature flags
98+
final newFeatureEnabled = await client.getBooleanFlag(
99+
'new-feature',
100+
defaultValue: false,
101+
);
102+
103+
// Use the returned flag value
104+
if (newFeatureEnabled) {
105+
print('New feature is enabled!');
106+
107+
final welcomeMessage = await client.getStringFlag(
108+
'welcome-message',
109+
defaultValue: 'Welcome!',
110+
);
111+
112+
print(welcomeMessage);
113+
}
114+
}
115+
```
62116

63117
### API Reference
64118

65-
See [TBD](TBD) for the complete API documentation.
119+
See [here](https://pub.dev/documentation/openfeature_dart_server_sdk/latest/) for the complete API documentation.
66120

67121
## 🌟 Features
68122

69-
| Status | Features | Description |
70-
| ------ |---------------------------------| --------------------------------------------------------------------------------------------------------------------------------- |
71-
| | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
72-
| | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
73-
| | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
74-
| | [Logging](#logging) | Integrate with popular logging packages. |
75-
| | [Domains](#domains) | Logically bind clients with providers.|
76-
| | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
77-
| | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
78-
| | [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) |
79-
| | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
123+
| Status | Features | Description |
124+
| ------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
125+
|| [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
126+
|| [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
127+
|| [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
128+
|| [Logging](#logging) | Integrate with popular logging packages. |
129+
|| [Domains](#domains) | Logically bind clients with providers. |
130+
|| [Eventing](#eventing) | React to state changes in the provider or flag management system. |
131+
|| [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
132+
|| [Transaction Context Propagation](#transaction-context-propagation) | Set a specific [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context) for a transaction (e.g. an HTTP request or a thread) |
133+
|| [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
80134

81135
<sub>Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌</sub>
82136

@@ -88,12 +142,43 @@ If the provider you're looking for hasn't been created yet, see the [develop a p
88142

89143
Once you've added a provider as a dependency, it can be registered with OpenFeature like this:
90144

145+
```dart
146+
final api = OpenFeatureAPI();
147+
api.setProvider(MyProvider());
148+
```
149+
91150
### Targeting
92151

93152
Sometimes, the value of a flag must consider some dynamic criteria about the application or user, such as the user's location, IP, email address, or the server's location.
94153
In OpenFeature, we refer to this as [targeting](https://openfeature.dev/specification/glossary#targeting).
95154
If the flag management system you're using supports targeting, you can provide the input data using the [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context).
96155

156+
```dart
157+
// Set a value to the global context
158+
final api = OpenFeatureAPI();
159+
api.setGlobalContext(OpenFeatureEvaluationContext({
160+
'region': 'us-east-1-iah-1a',
161+
}));
162+
163+
// Create a client with a specific evaluation context
164+
final client = FeatureClient(
165+
metadata: ClientMetadata(name: 'my-app'),
166+
hookManager: HookManager(),
167+
defaultContext: EvaluationContext(attributes: {
168+
'version': '1.4.6',
169+
}),
170+
);
171+
172+
// Set a value to the invocation context
173+
final result = await client.getBooleanFlag(
174+
'feature-flag',
175+
defaultValue: false,
176+
context: EvaluationContext(attributes: {
177+
'user': 'user-123',
178+
'company': 'Initech',
179+
}),
180+
);
181+
```
97182

98183
### Hooks
99184

@@ -103,13 +188,32 @@ If the hook you're looking for hasn't been created yet, see the [develop a hook]
103188

104189
Once you've added a hook as a dependency, it can be registered at the global, client, or flag invocation level.
105190

191+
```dart
192+
// Add a hook globally, to run on all evaluations
193+
final api = OpenFeatureAPI();
194+
api.addHooks([MyGlobalHook()]);
195+
196+
// Add a hook on this client, to run on all evaluations made by this client
197+
final hookManager = HookManager();
198+
hookManager.addHook(MyClientHook());
199+
200+
final client = FeatureClient(
201+
metadata: ClientMetadata(name: 'my-app'),
202+
hookManager: hookManager,
203+
defaultContext: EvaluationContext(attributes: {}),
204+
);
205+
206+
// Create a hook for a specific evaluation
207+
final myHook = MyHook();
208+
// You can use the hook with a specific evaluation
209+
```
210+
106211
### Tracking
107212

108213
The [tracking API](https://openfeature.dev/specification/sections/tracking/) allows you to use OpenFeature abstractions and objects to associate user actions with feature flag evaluations.
109214
This is essential for robust experimentation powered by feature flags.
110215
For example, a flag enhancing the appearance of a UI component might drive user engagement to a new feature; to test this hypothesis, telemetry collected by a [hook](#hooks) or [provider](#providers) can be associated with telemetry reported in the client's `track` function.
111216

112-
113217
Note that some providers may not support tracking; check the documentation for your provider for more information.
114218

115219
### Logging
@@ -123,11 +227,38 @@ This hook can be particularly helpful for troubleshooting and debugging; simply
123227

124228
##### Usage example
125229

230+
```dart
231+
import 'package:logging/logging.dart';
232+
import 'package:openfeature_dart_server_sdk/hooks.dart';
233+
234+
// Configure logging
235+
Logger.root.level = Level.ALL;
236+
Logger.root.onRecord.listen((record) {
237+
print('${record.time} [${record.level.name}] ${record.message}');
238+
});
239+
240+
// Create a logging hook
241+
final loggingHook = LoggingHook();
242+
243+
// Add the hook to your hook manager
244+
final hookManager = HookManager();
245+
hookManager.addHook(loggingHook);
246+
247+
// Create a client using this hook manager
248+
final client = FeatureClient(
249+
metadata: ClientMetadata(name: 'test-client'),
250+
hookManager: hookManager,
251+
defaultContext: EvaluationContext(attributes: {}),
252+
);
253+
254+
// Evaluate a flag
255+
final result = await client.getBooleanFlag('my-flag', defaultValue: false);
256+
```
126257

127258
###### Output
128259

129260
```sh
130-
{"time":"2024-10-23T13:33:09.8870867+03:00","level":"DEBUG","msg":"Before stage","domain":"test-client","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true}
261+
{"time":"2024-10-23T13:33:09.8870867+03:00","level":"DEBUG","msg":"Before stage","domain":"test-client","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true}
131262
{"time":"2024-10-23T13:33:09.8968242+03:00","level":"ERROR","msg":"Error stage","domain":"test-client","provider_name":"InMemoryProvider","flag_key":"not-exist","default_value":true,"error_message":"error code: FLAG_NOT_FOUND: flag for key not-exist not found"}
132263
```
133264

@@ -137,6 +268,26 @@ See [hooks](#hooks) for more information on configuring hooks.
137268

138269
Clients can be assigned to a domain. A domain is a logical identifier that can be used to associate clients with a particular provider. If a domain has no associated provider, the default provider is used.
139270

271+
```dart
272+
import 'package:openfeature_dart_server_sdk/domain_manager.dart';
273+
import 'package:openfeature_dart_server_sdk/feature_provider.dart';
274+
import 'package:openfeature_dart_server_sdk/open_feature_api.dart';
275+
276+
// Get the OpenFeature API instance
277+
final api = OpenFeatureAPI();
278+
279+
// Register the default provider
280+
api.setProvider(InMemoryProvider({'default-flag': true}));
281+
282+
// Register a domain-specific provider
283+
api.bindClientToProvider('cache-domain', 'CachedProvider');
284+
285+
// Client backed by default provider
286+
api.evaluateBooleanFlag('my-flag', 'default-client');
287+
288+
// Client backed by CachedProvider
289+
api.evaluateBooleanFlag('my-flag', 'cache-domain');
290+
```
140291

141292
### Eventing
142293

@@ -146,15 +297,91 @@ Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGE
146297

147298
Please refer to the documentation of the provider you're using to see what events are supported.
148299

300+
```dart
301+
import 'package:openfeature_dart_server_sdk/open_feature_api.dart';
302+
import 'package:openfeature_dart_server_sdk/open_feature_event.dart';
303+
304+
// Get the OpenFeature API instance
305+
final api = OpenFeatureAPI();
306+
307+
// Listen for provider change events
308+
api.events.listen((event) {
309+
if (event.type == OpenFeatureEventType.providerChanged) {
310+
print('Provider changed: ${event.message}');
311+
}
312+
});
313+
314+
// Listen for flag evaluation events
315+
api.events.listen((event) {
316+
if (event.type == OpenFeatureEventType.flagEvaluated) {
317+
print('Flag evaluated: ${event.data['flagKey']} = ${event.data['result']}');
318+
}
319+
});
320+
```
149321

150322
### Shutdown
151323

324+
The OpenFeature API provides mechanisms to perform a cleanup of all registered providers.
325+
This should only be called when your application is in the process of shutting down.
326+
327+
```dart
328+
import 'package:openfeature_dart_server_sdk/open_feature_api.dart';
329+
import 'package:openfeature_dart_server_sdk/shutdown_manager.dart';
330+
331+
// Get the OpenFeature API instance
332+
final api = OpenFeatureAPI();
333+
334+
// Register shutdown hooks
335+
final shutdownManager = ShutdownManager();
336+
shutdownManager.registerHook(ShutdownHook(
337+
name: 'provider-cleanup',
338+
phase: ShutdownPhase.PROVIDER_SHUTDOWN,
339+
execute: () async {
340+
// Clean up provider resources
341+
await api.dispose();
342+
},
343+
));
344+
345+
// During application shutdown
346+
await shutdownManager.shutdown();
347+
```
152348

153349
### Transaction Context Propagation
154350

155351
Transaction context is a container for transaction-specific evaluation context (e.g. user id, user agent, IP).
156352
Transaction context can be set where specific data is available (e.g. an auth service or request handler), and by using the transaction context propagator, it will automatically be applied to all flag evaluations within a transaction (e.g. a request or thread).
157353

354+
```dart
355+
import 'package:openfeature_dart_server_sdk/transaction_context.dart';
356+
357+
// Create a transaction context manager
358+
final transactionManager = TransactionContextManager();
359+
360+
// Set the transaction context
361+
final context = TransactionContext(
362+
transactionId: 'request-123',
363+
attributes: {
364+
'user': 'user-456',
365+
'region': 'us-west-1',
366+
},
367+
);
368+
transactionManager.pushContext(context);
369+
370+
// The transaction context will automatically be applied to flag evaluations
371+
await client.getBooleanFlag('my-flag', defaultValue: false);
372+
373+
// Execute code with a specific transaction context
374+
await transactionManager.withContext(
375+
'transaction-id',
376+
{'user': 'user-123'},
377+
() async {
378+
await client.getBooleanFlag('my-flag', defaultValue: false);
379+
},
380+
);
381+
382+
// When the transaction is complete, pop the context
383+
transactionManager.popContext();
384+
```
158385

159386
## Extending
160387

@@ -164,6 +391,111 @@ To develop a provider, you need to create a new project and include the OpenFeat
164391
This can be a new repository or included in [the existing contrib repository](https://github.com/open-feature/dart-server-sdk-contrib) available under the OpenFeature organization.
165392
You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK.
166393

394+
```dart
395+
import 'dart:async';
396+
import 'package:openfeature_dart_server_sdk/feature_provider.dart';
397+
398+
class MyCustomProvider implements FeatureProvider {
399+
@override
400+
String get name => 'MyCustomProvider';
401+
402+
@override
403+
ProviderState get state => ProviderState.READY;
404+
405+
@override
406+
ProviderConfig get config => ProviderConfig();
407+
408+
@override
409+
Future<void> initialize([Map<String, dynamic>? config]) async {
410+
// Initialize your provider
411+
}
412+
413+
@override
414+
Future<void> connect() async {
415+
// Connection logic if needed
416+
}
417+
418+
@override
419+
Future<void> shutdown() async {
420+
// Clean up resources
421+
}
422+
423+
@override
424+
Future<FlagEvaluationResult<bool>> getBooleanFlag(
425+
String flagKey,
426+
bool defaultValue, {
427+
Map<String, dynamic>? context,
428+
}) async {
429+
// Evaluate boolean flag
430+
return FlagEvaluationResult(
431+
flagKey: flagKey,
432+
value: true, // Your implementation here
433+
evaluatedAt: DateTime.now(),
434+
evaluatorId: name,
435+
);
436+
}
437+
438+
@override
439+
Future<FlagEvaluationResult<String>> getStringFlag(
440+
String flagKey,
441+
String defaultValue, {
442+
Map<String, dynamic>? context,
443+
}) async {
444+
// Evaluate string flag
445+
return FlagEvaluationResult(
446+
flagKey: flagKey,
447+
value: 'value', // Your implementation here
448+
evaluatedAt: DateTime.now(),
449+
evaluatorId: name,
450+
);
451+
}
452+
453+
@override
454+
Future<FlagEvaluationResult<int>> getIntegerFlag(
455+
String flagKey,
456+
int defaultValue, {
457+
Map<String, dynamic>? context,
458+
}) async {
459+
// Evaluate integer flag
460+
return FlagEvaluationResult(
461+
flagKey: flagKey,
462+
value: 42, // Your implementation here
463+
evaluatedAt: DateTime.now(),
464+
evaluatorId: name,
465+
);
466+
}
467+
468+
@override
469+
Future<FlagEvaluationResult<double>> getDoubleFlag(
470+
String flagKey,
471+
double defaultValue, {
472+
Map<String, dynamic>? context,
473+
}) async {
474+
// Evaluate double flag
475+
return FlagEvaluationResult(
476+
flagKey: flagKey,
477+
value: 3.14, // Your implementation here
478+
evaluatedAt: DateTime.now(),
479+
evaluatorId: name,
480+
);
481+
}
482+
483+
@override
484+
Future<FlagEvaluationResult<Map<String, dynamic>>> getObjectFlag(
485+
String flagKey,
486+
Map<String, dynamic> defaultValue, {
487+
Map<String, dynamic>? context,
488+
}) async {
489+
// Evaluate object flag
490+
return FlagEvaluationResult(
491+
flagKey: flagKey,
492+
value: {'key': 'value'}, // Your implementation here
493+
evaluatedAt: DateTime.now(),
494+
evaluatorId: name,
495+
);
496+
}
497+
}
498+
```
167499

168500
> Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs!
169501
@@ -175,6 +507,43 @@ Implement your own hook by conforming to the [Hook interface](./pkg/openfeature/
175507
To satisfy the interface, all methods (`Before`/`After`/`Finally`/`Error`) need to be defined.
176508
To avoid defining empty functions make use of the `UnimplementedHook` struct (which already implements all the empty functions).
177509

510+
```dart
511+
import 'dart:async';
512+
import 'package:openfeature_dart_server_sdk/hooks.dart';
513+
514+
class MyCustomHook extends BaseHook {
515+
MyCustomHook()
516+
: super(metadata: HookMetadata(name: 'MyCustomHook'));
517+
518+
@override
519+
Future<void> before(HookContext context) async {
520+
// Code to run before flag evaluation
521+
print('Before evaluating flag: ${context.flagKey}');
522+
}
523+
524+
@override
525+
Future<void> after(HookContext context) async {
526+
// Code to run after successful flag evaluation
527+
print('After evaluating flag: ${context.flagKey}, result: ${context.result}');
528+
}
529+
530+
@override
531+
Future<void> error(HookContext context) async {
532+
// Code to run when an error occurs during flag evaluation
533+
print('Error evaluating flag: ${context.flagKey}, error: ${context.error}');
534+
}
535+
536+
@override
537+
Future<void> finally_(
538+
HookContext context,
539+
EvaluationDetails? evaluationDetails, [
540+
HookHints? hints,
541+
]) async {
542+
// Code to run regardless of success or failure
543+
print('Finished evaluating flag: ${context.flagKey}');
544+
}
545+
}
546+
```
178547

179548
> Built a new hook? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=hook&projects=&template=document-hook.yaml&title=%5BHook%5D%3A+) so we can add it to the docs!
180549
@@ -185,8 +554,44 @@ The `TestProvider` is thread-safe and can be used in tests that run in parallel.
185554

186555
Call `testProvider.UsingFlags(t, tt.flags)` to set flags for a test, and clean them up with `testProvider.Cleanup()`
187556

557+
```dart
558+
import 'package:test/test.dart';
559+
import 'package:openfeature_dart_server_sdk/open_feature_api.dart';
560+
import 'package:openfeature_dart_server_sdk/feature_provider.dart';
561+
562+
void main() {
563+
late OpenFeatureAPI api;
564+
late InMemoryProvider testProvider;
565+
566+
setUp(() {
567+
api = OpenFeatureAPI();
568+
testProvider = InMemoryProvider({
569+
'test-flag': true,
570+
'string-flag': 'test-value',
571+
});
572+
api.setProvider(testProvider);
573+
});
574+
575+
tearDown(() {
576+
OpenFeatureAPI.resetInstance();
577+
});
578+
579+
test('evaluates boolean flag correctly', () async {
580+
final client = api.getClient('test-client');
581+
final result = await client.getBooleanFlag('test-flag', defaultValue: false);
582+
expect(result, isTrue);
583+
});
584+
585+
test('evaluates string flag correctly', () async {
586+
final client = api.getClient('test-client');
587+
final result = await client.getStringFlag('string-flag', defaultValue: 'default');
588+
expect(result, equals('test-value'));
589+
});
590+
}
591+
```
188592

189593
<!-- x-hide-in-docs-start -->
594+
190595
## ⭐️ Support the project
191596

192597
- Give this repo a ⭐️!
@@ -207,4 +612,5 @@ Interested in contributing? Great, we'd love your help! To get started, take a l
207612
</a>
208613

209614
Made with [contrib.rocks](https://contrib.rocks).
615+
210616
<!-- x-hide-in-docs-end -->

0 commit comments

Comments
 (0)
Please sign in to comment.