Skip to content
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

feat!: domain instead of client name #294

Merged
merged 3 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,16 @@ public async Task Example()

## 🌟 Features

| Status | Features | Description |
| ------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
| ✅ | [Logging](#logging) | Integrate with popular logging packages. |
| ✅ | [Named clients](#named-clients) | Utilize multiple providers in a single application. |
| ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |
| Status | Features | Description |
| ------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| ✅ | [Providers](#providers) | Integrate with a commercial, open source, or in-house feature management tool. |
| ✅ | [Targeting](#targeting) | Contextually-aware flag evaluation using [evaluation context](https://openfeature.dev/docs/reference/concepts/evaluation-context). |
| ✅ | [Hooks](#hooks) | Add functionality to various stages of the flag evaluation life-cycle. |
| ✅ | [Logging](#logging) | Integrate with popular logging packages. |
| ✅ | [Domains](#domains) | Logically bind clients with providers. |
| ✅ | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
| ✅ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |

> Implemented: ✅ | In-progress: ⚠️ | Not implemented yet: ❌

Expand All @@ -96,7 +96,7 @@ await Api.Instance.SetProviderAsync(new MyProvider());
```

In some situations, it may be beneficial to register multiple providers in the same application.
This is possible using [named clients](#named-clients), which is covered in more detail below.
This is possible using [domains](#domains), which is covered in more detail below.

### Targeting

Expand Down Expand Up @@ -151,27 +151,29 @@ var value = await client.GetBooleanValueAsync("boolFlag", false, context, new Fl

The .NET SDK uses Microsoft.Extensions.Logging. See the [manual](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging?tabs=command-line) for complete documentation.

### Named clients
### Domains

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

```csharp
// registering the default provider
await Api.Instance.SetProviderAsync(new LocalProvider());

// registering a named provider
// registering a provider to a domain
await Api.Instance.SetProviderAsync("clientForCache", new CachedProvider());

// a client backed by default provider
FeatureClient clientDefault = Api.Instance.GetClient();

// a client backed by CachedProvider
FeatureClient clientNamed = Api.Instance.GetClient("clientForCache");

FeatureClient scopedClient = Api.Instance.GetClient("clientForCache");
```

Domains can be defined on a provider during registration.
For more details, please refer to the [providers](#providers) section.

### Eventing

Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes,
Expand Down
32 changes: 17 additions & 15 deletions src/OpenFeature/Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,19 @@ public async Task SetProviderAsync(FeatureProvider featureProvider)
}

/// <summary>
/// Sets the feature provider to given clientName. In order to wait for the provider to be set, and
/// Binds the feature provider to the given domain. In order to wait for the provider to be set, and
/// initialization to complete, await the returned task.
/// </summary>
/// <param name="clientName">Name of client</param>
/// <param name="domain">An identifier which logically binds clients with providers</param>
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
public async Task SetProviderAsync(string clientName, FeatureProvider featureProvider)
public async Task SetProviderAsync(string domain, FeatureProvider featureProvider)
{
if (string.IsNullOrWhiteSpace(clientName))
if (string.IsNullOrWhiteSpace(domain))
{
throw new ArgumentNullException(nameof(clientName));
throw new ArgumentNullException(nameof(domain));
}
this._eventExecutor.RegisterClientFeatureProvider(clientName, featureProvider);
await this._repository.SetProviderAsync(clientName, featureProvider, this.GetContext(), this.AfterInitialization, this.AfterError).ConfigureAwait(false);
this._eventExecutor.RegisterClientFeatureProvider(domain, featureProvider);
await this._repository.SetProviderAsync(domain, featureProvider, this.GetContext(), this.AfterInitialization, this.AfterError).ConfigureAwait(false);
}

/// <summary>
Expand All @@ -82,14 +82,15 @@ public FeatureProvider GetProvider()
}

/// <summary>
/// Gets the feature provider with given clientName
/// Gets the feature provider with given domain
/// </summary>
/// <param name="clientName">Name of client</param>
/// <returns>A provider associated with the given clientName, if clientName is empty or doesn't
/// <param name="domain">An identifier which logically binds clients with providers</param>

/// <returns>A provider associated with the given domain, if domain is empty or doesn't
/// have a corresponding provider the default provider will be returned</returns>
public FeatureProvider GetProvider(string clientName)
public FeatureProvider GetProvider(string domain)
{
return this._repository.GetProvider(clientName);
return this._repository.GetProvider(domain);
}

/// <summary>
Expand All @@ -104,12 +105,13 @@ public FeatureProvider GetProvider(string clientName)
public Metadata? GetProviderMetadata() => this.GetProvider().GetMetadata();

/// <summary>
/// Gets providers metadata assigned to the given clientName. If the clientName has no provider
/// Gets providers metadata assigned to the given domain. If the domain has no provider
/// assigned to it the default provider will be returned
/// </summary>
/// <param name="clientName">Name of client</param>
/// <param name="domain">An identifier which logically binds clients with providers</param>

/// <returns>Metadata assigned to provider</returns>
public Metadata? GetProviderMetadata(string clientName) => this.GetProvider(clientName).GetMetadata();
public Metadata? GetProviderMetadata(string domain) => this.GetProvider(domain).GetMetadata();

/// <summary>
/// Create a new instance of <see cref="FeatureClient"/> using the current provider
Expand Down
22 changes: 11 additions & 11 deletions src/OpenFeature/ProviderRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private static async Task InitProviderAsync(
/// <summary>
/// Set a named provider
/// </summary>
/// <param name="clientName">the name to associate with the provider</param>
/// <param name="domain">an identifier which logically binds clients with providers</param>
/// <param name="featureProvider">the provider to set as the default, passing null has no effect</param>
/// <param name="context">the context to initialize the provider with</param>
/// <param name="afterInitSuccess">
Expand All @@ -138,15 +138,15 @@ private static async Task InitProviderAsync(
/// initialization
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel any async side effects.</param>
public async Task SetProviderAsync(string? clientName,
public async Task SetProviderAsync(string? domain,
FeatureProvider? featureProvider,
EvaluationContext context,
Func<FeatureProvider, Task>? afterInitSuccess = null,
Func<FeatureProvider, Exception, Task>? afterInitError = null,
CancellationToken cancellationToken = default)
{
// Cannot set a provider for a null clientName.
if (clientName == null)
// Cannot set a provider for a null domain.
if (domain == null)
{
return;
}
Expand All @@ -155,17 +155,17 @@ public async Task SetProviderAsync(string? clientName,

try
{
this._featureProviders.TryGetValue(clientName, out var oldProvider);
this._featureProviders.TryGetValue(domain, out var oldProvider);
if (featureProvider != null)
{
this._featureProviders.AddOrUpdate(clientName, featureProvider,
this._featureProviders.AddOrUpdate(domain, featureProvider,
(key, current) => featureProvider);
}
else
{
// If names of clients are programmatic, then setting the provider to null could result
// in unbounded growth of the collection.
this._featureProviders.TryRemove(clientName, out _);
this._featureProviders.TryRemove(domain, out _);
}

// We want to allow shutdown to happen concurrently with initialization, and the caller to not
Expand Down Expand Up @@ -238,22 +238,22 @@ public FeatureProvider GetProvider()
}
}

public FeatureProvider GetProvider(string? clientName)
public FeatureProvider GetProvider(string? domain)
{
#if NET6_0_OR_GREATER
if (string.IsNullOrEmpty(clientName))
if (string.IsNullOrEmpty(domain))
{
return this.GetProvider();
}
#else
// This is a workaround for the issue in .NET Framework where string.IsNullOrEmpty is not nullable compatible.
if (clientName == null || string.IsNullOrEmpty(clientName))
if (domain == null || string.IsNullOrEmpty(domain))
{
return this.GetProvider();
}
#endif

return this._featureProviders.TryGetValue(clientName, out var featureProvider)
return this._featureProviders.TryGetValue(domain, out var featureProvider)
? featureProvider
: this.GetProvider();
}
Expand Down
6 changes: 3 additions & 3 deletions test/OpenFeature.Benchmarks/OpenFeatureClientBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace OpenFeature.Benchmark
[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task")]
public class OpenFeatureClientBenchmarks
{
private readonly string _clientName;
private readonly string _domain;
private readonly string _clientVersion;
private readonly string _flagName;
private readonly bool _defaultBoolValue;
Expand All @@ -30,7 +30,7 @@ public class OpenFeatureClientBenchmarks
public OpenFeatureClientBenchmarks()
{
var fixture = new Fixture();
this._clientName = fixture.Create<string>();
this._domain = fixture.Create<string>();
this._clientVersion = fixture.Create<string>();
this._flagName = fixture.Create<string>();
this._defaultBoolValue = fixture.Create<bool>();
Expand All @@ -40,7 +40,7 @@ public OpenFeatureClientBenchmarks()
this._defaultStructureValue = fixture.Create<Value>();
this._emptyFlagOptions = new FlagEvaluationOptions(ImmutableList<Hook>.Empty, ImmutableDictionary<string, object>.Empty);

this._client = Api.Instance.GetClient(this._clientName, this._clientVersion);
this._client = Api.Instance.GetClient(this._domain, this._clientVersion);
}

[Benchmark]
Expand Down
Loading