|
| 1 | +# OpenFeature Integration |
| 2 | + |
| 3 | +ExperimentFramework supports [OpenFeature](https://openfeature.dev/), an open standard for feature flag management. This allows integration with any OpenFeature-compatible provider such as LaunchDarkly, Flagsmith, CloudBees, or custom providers. |
| 4 | + |
| 5 | +## Configuration |
| 6 | + |
| 7 | +Use `UsingOpenFeature()` to configure an experiment to use OpenFeature for trial selection: |
| 8 | + |
| 9 | +```csharp |
| 10 | +services.AddExperimentFramework( |
| 11 | + ExperimentFrameworkBuilder.Create() |
| 12 | + .Define<IPaymentProcessor>(c => c |
| 13 | + .UsingOpenFeature("payment-processor-experiment") |
| 14 | + .AddDefaultTrial<StripeProcessor>("stripe") |
| 15 | + .AddTrial<PayPalProcessor>("paypal") |
| 16 | + .AddTrial<SquareProcessor>("square")) |
| 17 | + .UseDispatchProxy()); |
| 18 | +``` |
| 19 | + |
| 20 | +## Flag Key Naming |
| 21 | + |
| 22 | +When no flag key is specified, the framework generates a kebab-case name from the service type: |
| 23 | + |
| 24 | +| Service Type | Generated Flag Key | |
| 25 | +|--------------|-------------------| |
| 26 | +| `IPaymentProcessor` | `payment-processor` | |
| 27 | +| `IUserService` | `user-service` | |
| 28 | +| `IDataRepository` | `data-repository` | |
| 29 | + |
| 30 | +You can override this with an explicit flag key: |
| 31 | + |
| 32 | +```csharp |
| 33 | +.UsingOpenFeature("my-custom-flag-key") |
| 34 | +``` |
| 35 | + |
| 36 | +## Boolean vs String Flags |
| 37 | + |
| 38 | +The framework automatically detects the flag type based on trial keys: |
| 39 | + |
| 40 | +**Boolean flags** (when trials are "true" and "false"): |
| 41 | + |
| 42 | +```csharp |
| 43 | +.Define<IFeature>(c => c |
| 44 | + .UsingOpenFeature("new-feature") |
| 45 | + .AddDefaultTrial<LegacyFeature>("false") |
| 46 | + .AddTrial<NewFeature>("true")) |
| 47 | +``` |
| 48 | + |
| 49 | +Uses `GetBooleanValueAsync()` from OpenFeature. |
| 50 | + |
| 51 | +**String flags** (multi-variant): |
| 52 | + |
| 53 | +```csharp |
| 54 | +.Define<IAlgorithm>(c => c |
| 55 | + .UsingOpenFeature("algorithm-variant") |
| 56 | + .AddDefaultTrial<ControlAlgorithm>("control") |
| 57 | + .AddTrial<VariantA>("variant-a") |
| 58 | + .AddTrial<VariantB>("variant-b")) |
| 59 | +``` |
| 60 | + |
| 61 | +Uses `GetStringValueAsync()` from OpenFeature. |
| 62 | + |
| 63 | +## Provider Setup |
| 64 | + |
| 65 | +Configure your OpenFeature provider before using ExperimentFramework: |
| 66 | + |
| 67 | +```csharp |
| 68 | +// Example with InMemoryProvider for testing |
| 69 | +await Api.Instance.SetProviderAsync(new InMemoryProvider(new Dictionary<string, Flag> |
| 70 | +{ |
| 71 | + { "payment-processor-experiment", new Flag<string>("paypal") }, |
| 72 | + { "new-feature", new Flag<bool>(true) } |
| 73 | +})); |
| 74 | +``` |
| 75 | + |
| 76 | +For production, use your preferred provider: |
| 77 | + |
| 78 | +```csharp |
| 79 | +// LaunchDarkly example |
| 80 | +await Api.Instance.SetProviderAsync( |
| 81 | + new LaunchDarklyProvider(Configuration.Builder("sdk-key").Build())); |
| 82 | + |
| 83 | +// Flagsmith example |
| 84 | +await Api.Instance.SetProviderAsync( |
| 85 | + new FlagsmithProvider(new FlagsmithConfiguration { ApiUrl = "..." })); |
| 86 | +``` |
| 87 | + |
| 88 | +## Fallback Behavior |
| 89 | + |
| 90 | +When OpenFeature is not configured or flag evaluation fails, the framework falls back to the default trial. This provides resilience during: |
| 91 | + |
| 92 | +- Provider initialization |
| 93 | +- Network failures |
| 94 | +- Missing flag definitions |
| 95 | +- Invalid flag values |
| 96 | + |
| 97 | +## Evaluation Context |
| 98 | + |
| 99 | +OpenFeature evaluation context can be set globally or per-client. The framework uses the default client context: |
| 100 | + |
| 101 | +```csharp |
| 102 | +// Set global context |
| 103 | +Api.Instance.SetContext(new EvaluationContextBuilder() |
| 104 | + .Set("userId", "user-123") |
| 105 | + .Set("region", "us-east-1") |
| 106 | + .Build()); |
| 107 | +``` |
| 108 | + |
| 109 | +## Soft Dependency |
| 110 | + |
| 111 | +OpenFeature is a soft dependency - the framework uses reflection to access OpenFeature APIs. This means: |
| 112 | + |
| 113 | +- No compile-time dependency on the OpenFeature package |
| 114 | +- Graceful fallback when OpenFeature is not installed |
| 115 | +- Works with any OpenFeature SDK version |
| 116 | + |
| 117 | +To use OpenFeature, add the package to your project: |
| 118 | + |
| 119 | +```bash |
| 120 | +dotnet add package OpenFeature |
| 121 | +``` |
| 122 | + |
| 123 | +## Example: Complete Setup |
| 124 | + |
| 125 | +```csharp |
| 126 | +// Program.cs |
| 127 | +var builder = WebApplication.CreateBuilder(args); |
| 128 | + |
| 129 | +// Configure OpenFeature provider |
| 130 | +await Api.Instance.SetProviderAsync(new YourProvider()); |
| 131 | + |
| 132 | +// Configure experiments |
| 133 | +builder.Services.AddExperimentFramework( |
| 134 | + ExperimentFrameworkBuilder.Create() |
| 135 | + .Define<IRecommendationEngine>(c => c |
| 136 | + .UsingOpenFeature("recommendation-algorithm") |
| 137 | + .AddDefaultTrial<CollaborativeFiltering>("collaborative") |
| 138 | + .AddTrial<ContentBased>("content-based") |
| 139 | + .AddTrial<HybridApproach>("hybrid") |
| 140 | + .OnErrorRedirectAndReplayDefault()) |
| 141 | + .UseDispatchProxy()); |
| 142 | + |
| 143 | +var app = builder.Build(); |
| 144 | +``` |
| 145 | + |
| 146 | +## Combining with Other Selection Modes |
| 147 | + |
| 148 | +You can use different selection modes for different services in the same application: |
| 149 | + |
| 150 | +```csharp |
| 151 | +ExperimentFrameworkBuilder.Create() |
| 152 | + // OpenFeature for external flag management |
| 153 | + .Define<IPaymentProcessor>(c => c |
| 154 | + .UsingOpenFeature("payment-experiment") |
| 155 | + .AddDefaultTrial<StripeProcessor>("stripe") |
| 156 | + .AddTrial<PayPalProcessor>("paypal")) |
| 157 | + |
| 158 | + // Microsoft Feature Management for internal flags |
| 159 | + .Define<ISearchService>(c => c |
| 160 | + .UsingFeatureFlag("SearchV2") |
| 161 | + .AddDefaultTrial<LegacySearch>("false") |
| 162 | + .AddTrial<NewSearch>("true")) |
| 163 | + |
| 164 | + // Configuration for static routing |
| 165 | + .Define<ILogger>(c => c |
| 166 | + .UsingConfigurationKey("Logging:Provider") |
| 167 | + .AddDefaultTrial<ConsoleLogger>("console") |
| 168 | + .AddTrial<FileLogger>("file")) |
| 169 | +``` |
0 commit comments