Skip to content

Commit

Permalink
feat!: domain instead of client name (open-feature#294)
Browse files Browse the repository at this point in the history
Uses "domain" terminology instead of "client name / named client".

Fixes: open-feature#249

I believe with this, we are able to release a 2.0

---------

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com>
  • Loading branch information
toddbaert and askpt authored Aug 13, 2024
1 parent 00e99d6 commit 4c0592e
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 85 deletions.
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

0 comments on commit 4c0592e

Please sign in to comment.