From de9d787a773f84187477d2dd1f79bca29c157ef4 Mon Sep 17 00:00:00 2001 From: Vladimir Petrusevici Date: Fri, 27 Oct 2023 11:38:28 +0300 Subject: [PATCH] Using factory for provider creation --- OpenFeature.sln | 6 ++- src/OpenFeature/Api.cs | 48 +++++++++++++++---- src/OpenFeature/OpenFeatureClient.cs | 8 ++-- .../OpenFeatureClientTests.cs | 2 +- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/OpenFeature.sln b/OpenFeature.sln index 5ed0e809..6ff20582 100644 --- a/OpenFeature.sln +++ b/OpenFeature.sln @@ -36,7 +36,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Tests", "test\O EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.Benchmarks", "test\OpenFeature.Benchmarks\OpenFeature.Benchmarks.csproj", "{90E7EAD3-251E-4490-AF78-E758E33518E5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.E2ETests", "test\OpenFeature.E2ETests\OpenFeature.E2ETests.csproj", "{90E7EAD3-251E-4490-AF78-E758E33518E5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenFeature.E2ETests", "test\OpenFeature.E2ETests\OpenFeature.E2ETests.csproj", "{CB82B298-040A-4E8B-B74E-1636DC5C2457}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -56,6 +56,10 @@ Global {90E7EAD3-251E-4490-AF78-E758E33518E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {90E7EAD3-251E-4490-AF78-E758E33518E5}.Release|Any CPU.ActiveCfg = Release|Any CPU {90E7EAD3-251E-4490-AF78-E758E33518E5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB82B298-040A-4E8B-B74E-1636DC5C2457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB82B298-040A-4E8B-B74E-1636DC5C2457}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB82B298-040A-4E8B-B74E-1636DC5C2457}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB82B298-040A-4E8B-B74E-1636DC5C2457}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/OpenFeature/Api.cs b/src/OpenFeature/Api.cs index e679bf75..8120ab82 100644 --- a/src/OpenFeature/Api.cs +++ b/src/OpenFeature/Api.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -15,9 +16,9 @@ namespace OpenFeature public sealed class Api { private EvaluationContext _evaluationContext = EvaluationContext.Empty; - private FeatureProvider _defaultProvider = new NoOpFeatureProvider(); - private readonly ConcurrentDictionary _featureProviders = - new ConcurrentDictionary(); + private Func _defaultProviderFunc = () => new NoOpFeatureProvider(); + private readonly ConcurrentDictionary> _featureProviders = + new ConcurrentDictionary> (); private readonly ConcurrentStack _hooks = new ConcurrentStack(); /// The reader/writer locks are not disposed because the singleton instance should never be disposed. @@ -44,7 +45,7 @@ public void SetProvider(FeatureProvider featureProvider) this._featureProviderLock.EnterWriteLock(); try { - this._defaultProvider = featureProvider ?? this._defaultProvider; + this._defaultProviderFunc = featureProvider != null ? () => featureProvider : this._defaultProviderFunc; } finally { @@ -59,8 +60,37 @@ public void SetProvider(FeatureProvider featureProvider) /// Implementation of public void SetProvider(string clientName, FeatureProvider featureProvider) { - this._featureProviders.AddOrUpdate(clientName, featureProvider, - (key, current) => featureProvider); + FeatureProvider func() => featureProvider; + this._featureProviders.AddOrUpdate(clientName, func, + (key, current) => func); + } + + /// + /// Sets the feature provider + /// + /// A function to create new + public void SetProvider(Func featureProviderFactory) + { + this._featureProviderLock.EnterWriteLock(); + try + { + this._defaultProviderFunc = featureProviderFactory ?? this._defaultProviderFunc; + } + finally + { + this._featureProviderLock.ExitWriteLock(); + } + } + + /// + /// Sets the feature provider to given clientName + /// + /// Name of client + /// A function to create new + public void SetProvider(string clientName, Func featureProviderFactory) + { + this._featureProviders.AddOrUpdate(clientName, featureProviderFactory, + (key, current) => featureProviderFactory); } /// @@ -79,7 +109,7 @@ public FeatureProvider GetProvider() this._featureProviderLock.EnterReadLock(); try { - return this._defaultProvider; + return this._defaultProviderFunc(); } finally { @@ -100,8 +130,8 @@ public FeatureProvider GetProvider(string clientName) return this.GetProvider(); } - return this._featureProviders.TryGetValue(clientName, out var featureProvider) - ? featureProvider + return this._featureProviders.TryGetValue(clientName, out var featureProviderFunc) + ? featureProviderFunc() : this.GetProvider(); } diff --git a/src/OpenFeature/OpenFeatureClient.cs b/src/OpenFeature/OpenFeatureClient.cs index 1cc26802..30916488 100644 --- a/src/OpenFeature/OpenFeatureClient.cs +++ b/src/OpenFeature/OpenFeatureClient.cs @@ -23,6 +23,7 @@ public sealed class FeatureClient : IFeatureClient private EvaluationContext _evaluationContext; private readonly object _evaluationContextLock = new object(); + private FeatureProvider _featureProvider; /// /// Get a provider and an associated typed flag resolution method. @@ -40,11 +41,10 @@ public sealed class FeatureClient : IFeatureClient ExtractProvider( Func>>> method) { - // Alias the provider reference so getting the method and returning the provider are - // guaranteed to be the same object. - var provider = Api.Instance.GetProvider(this._metadata.Name); + // Will use factory to get provider if it's not created for this client. + this._featureProvider = this._featureProvider ?? Api.Instance.GetProvider(this._metadata.Name); - return (method(provider), provider); + return (method(this._featureProvider), this._featureProvider); } /// diff --git a/test/OpenFeature.Tests/OpenFeatureClientTests.cs b/test/OpenFeature.Tests/OpenFeatureClientTests.cs index 4995423e..af662c72 100644 --- a/test/OpenFeature.Tests/OpenFeatureClientTests.cs +++ b/test/OpenFeature.Tests/OpenFeatureClientTests.cs @@ -351,7 +351,7 @@ public async Task When_Exception_Occurs_During_Evaluation_Should_Return_Error() [Fact] public async Task Should_Use_No_Op_When_Provider_Is_Null() { - Api.Instance.SetProvider(null); + Api.Instance.SetProvider((FeatureProvider)null); var client = new FeatureClient("test", "test"); (await client.GetIntegerValue("some-key", 12)).Should().Be(12); }