Skip to content

Commit 1a3846d

Browse files
authored
fix: update DI lifecycle to use container instead of static instance (#534)
* feat: Update Api constructor visibility and adjust service registration Signed-off-by: André Silva <[email protected]> * feat: Enhance FeatureClient to use Api instance for provider access and context retrieval Signed-off-by: André Silva <[email protected]> * feat: Update Api to support singleton instance setup in Dependency Injection Signed-off-by: André Silva <[email protected]> * feat: Refactor FeatureClient constructor to prioritize API instance and improve provider access Signed-off-by: André Silva <[email protected]> --------- Signed-off-by: André Silva <[email protected]>
1 parent 2547a57 commit 1a3846d

File tree

4 files changed

+27
-13
lines changed

4 files changed

+27
-13
lines changed

src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public static IServiceCollection AddOpenFeature(this IServiceCollection services
2424
Guard.ThrowIfNull(configure);
2525

2626
// Register core OpenFeature services as singletons.
27-
services.TryAddSingleton(Api.Instance);
27+
var api = new Api();
28+
Api.SetInstance(api);
29+
services.TryAddSingleton(api);
2830
services.TryAddSingleton<IFeatureLifecycleManager, FeatureLifecycleManager>();
2931

3032
var builder = new OpenFeatureBuilder(services);

src/OpenFeature/Api.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public sealed class Api : IEventBus
3232
// not to mark type as beforeFieldInit
3333
// IE Lazy way of ensuring this is thread safe without using locks
3434
static Api() { }
35-
private Api() { }
35+
internal Api() { }
3636

3737
/// <summary>
3838
/// Sets the default feature provider. In order to wait for the provider to be set, and initialization to complete,
@@ -121,7 +121,7 @@ public FeatureProvider GetProvider(string domain)
121121
/// <returns><see cref="FeatureClient"/></returns>
122122
public FeatureClient GetClient(string? name = null, string? version = null, ILogger? logger = null,
123123
EvaluationContext? context = null) =>
124-
new FeatureClient(() => this._repository.GetProvider(name), name, version, logger, context);
124+
new FeatureClient(this, () => this._repository.GetProvider(name), name, version, logger, context);
125125

126126
/// <summary>
127127
/// Appends list of hooks to global hooks list
@@ -360,4 +360,12 @@ internal static void ResetApi()
360360
{
361361
Instance = new Api();
362362
}
363+
364+
/// <summary>
365+
/// This method should only be used in the Dependency Injection setup. It will set the singleton instance of the API using the provided instance.
366+
/// </summary>
367+
internal static void SetInstance(Api api)
368+
{
369+
Instance = api;
370+
}
363371
}

src/OpenFeature/OpenFeature.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
<InternalsVisibleTo Include="OpenFeature.Benchmarks" />
2020
<InternalsVisibleTo Include="OpenFeature.Tests" />
2121
<InternalsVisibleTo Include="OpenFeature.E2ETests" />
22+
<InternalsVisibleTo Include="OpenFeature.DependencyInjection" />
2223
<None Include="../../README.md" Pack="true" PackagePath="/" />
2324
</ItemGroup>
2425

25-
</Project>
26+
</Project>

src/OpenFeature/OpenFeatureClient.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public sealed partial class FeatureClient : IFeatureClient
1818
private readonly ConcurrentStack<Hook> _hooks = new ConcurrentStack<Hook>();
1919
private readonly ILogger _logger;
2020
private readonly Func<FeatureProvider> _providerAccessor;
21+
private readonly Api _api;
2122
private EvaluationContext _evaluationContext;
2223

2324
private readonly object _evaluationContextLock = new object();
@@ -40,7 +41,7 @@ public sealed partial class FeatureClient : IFeatureClient
4041
{
4142
// Alias the provider reference so getting the method and returning the provider are
4243
// guaranteed to be the same object.
43-
var provider = Api.Instance.GetProvider(this._metadata.Name!);
44+
var provider = this._api.GetProvider(this._metadata.Name!);
4445

4546
return (method(provider), provider);
4647
}
@@ -69,18 +70,20 @@ public void SetContext(EvaluationContext? context)
6970
/// <summary>
7071
/// Initializes a new instance of the <see cref="FeatureClient"/> class.
7172
/// </summary>
73+
/// <param name="api">The API instance for accessing global state and providers</param>
7274
/// <param name="providerAccessor">Function to retrieve current provider</param>
7375
/// <param name="name">Name of client <see cref="ClientMetadata"/></param>
7476
/// <param name="version">Version of client <see cref="ClientMetadata"/></param>
7577
/// <param name="logger">Logger used by client</param>
7678
/// <param name="context">Context given to this client</param>
7779
/// <exception cref="ArgumentNullException">Throws if any of the required parameters are null</exception>
78-
internal FeatureClient(Func<FeatureProvider> providerAccessor, string? name, string? version, ILogger? logger = null, EvaluationContext? context = null)
80+
internal FeatureClient(Api api, Func<FeatureProvider> providerAccessor, string? name, string? version, ILogger? logger = null, EvaluationContext? context = null)
7981
{
82+
this._api = api;
83+
this._providerAccessor = providerAccessor;
8084
this._metadata = new ClientMetadata(name, version);
8185
this._logger = logger ?? NullLogger<FeatureClient>.Instance;
8286
this._evaluationContext = context ?? EvaluationContext.Empty;
83-
this._providerAccessor = providerAccessor;
8487
}
8588

8689
/// <inheritdoc />
@@ -99,13 +102,13 @@ internal FeatureClient(Func<FeatureProvider> providerAccessor, string? name, str
99102
/// <inheritdoc />
100103
public void AddHandler(ProviderEventTypes eventType, EventHandlerDelegate handler)
101104
{
102-
Api.Instance.AddClientHandler(this._metadata.Name!, eventType, handler);
105+
this._api.AddClientHandler(this._metadata.Name!, eventType, handler);
103106
}
104107

105108
/// <inheritdoc />
106109
public void RemoveHandler(ProviderEventTypes type, EventHandlerDelegate handler)
107110
{
108-
Api.Instance.RemoveClientHandler(this._metadata.Name!, type, handler);
111+
this._api.RemoveClientHandler(this._metadata.Name!, type, handler);
109112
}
110113

111114
/// <inheritdoc />
@@ -213,13 +216,13 @@ private async Task<FlagEvaluationDetails<T>> EvaluateFlagAsync<T>(
213216

214217
// merge api, client, transaction and invocation context
215218
var evaluationContextBuilder = EvaluationContext.Builder();
216-
evaluationContextBuilder.Merge(Api.Instance.GetContext()); // API context
219+
evaluationContextBuilder.Merge(this._api.GetContext()); // API context
217220
evaluationContextBuilder.Merge(this.GetContext()); // Client context
218-
evaluationContextBuilder.Merge(Api.Instance.GetTransactionContext()); // Transaction context
221+
evaluationContextBuilder.Merge(this._api.GetTransactionContext()); // Transaction context
219222
evaluationContextBuilder.Merge(context); // Invocation context
220223

221224
var allHooks = ImmutableList.CreateBuilder<Hook>()
222-
.Concat(Api.Instance.GetHooks())
225+
.Concat(this._api.GetHooks())
223226
.Concat(this.GetHooks())
224227
.Concat(options?.Hooks ?? Enumerable.Empty<Hook>())
225228
.Concat(provider.GetProviderHooks())
@@ -310,7 +313,7 @@ public void Track(string trackingEventName, EvaluationContext? evaluationContext
310313
throw new ArgumentException("Tracking event cannot be null or empty.", nameof(trackingEventName));
311314
}
312315

313-
var globalContext = Api.Instance.GetContext();
316+
var globalContext = this._api.GetContext();
314317
var clientContext = this.GetContext();
315318

316319
var evaluationContextBuilder = EvaluationContext.Builder()

0 commit comments

Comments
 (0)