Skip to content

Commit

Permalink
feat: add support for eventing (#166)
Browse files Browse the repository at this point in the history
Signed-off-by: Florian Bacher <florian.bacher@dynatrace.com>
  • Loading branch information
bacherfl committed Jan 16, 2024
1 parent d0c25af commit f5fc1dd
Show file tree
Hide file tree
Showing 14 changed files with 1,054 additions and 11 deletions.
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public async Task Example()
|| [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. |
| | [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. |

Expand Down Expand Up @@ -167,6 +167,43 @@ client.AddHooks(new ExampleClientHook());
var value = await client.GetBooleanValue("boolFlag", false, context, new FlagEvaluationOptions(new ExampleInvocationHook()));
```

### Eventing

Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes,
provider readiness, or error conditions.
Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider.
Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`.

Please refer to the documentation of the provider you're using to see what events are supported.

Example usage of an Event handler:

```csharp
public static void EventHandler(ProviderEventPayload eventDetails)
{
Console.WriteLine(eventDetails.Type);
}
```

```csharp
EventHandlerDelegate callback = EventHandler;
// add an implementation of the EventHandlerDelegate for the PROVIDER_READY event
Api.Instance.AddHandler(ProviderEventTypes.ProviderReady, callback);
```

It is also possible to register an event handler for a specific client, as in the following example:

```csharp
EventHandlerDelegate callback = EventHandler;

var myClient = Api.Instance.GetClient("my-client");

var provider = new ExampleProvider();
await Api.Instance.SetProvider(myClient.GetMetadata().Name, provider);

myClient.AddHandler(ProviderEventTypes.ProviderReady, callback);
```

### Logging

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.
Expand Down
1 change: 1 addition & 0 deletions build/Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@

<ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="[1.7.1, 8.0.0)" />
<PackageReference Include="System.Threading.Channels" Version="[6.0.0, 8.0.0)" />
</ItemGroup>
</Project>
29 changes: 28 additions & 1 deletion src/OpenFeature/Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenFeature.Constant;
using OpenFeature.Model;

namespace OpenFeature
Expand All @@ -13,7 +14,7 @@ namespace OpenFeature
/// In the absence of a provider the evaluation API uses the "No-op provider", which simply returns the supplied default flag value.
/// </summary>
/// <seealso href="https://github.com/open-feature/spec/blob/v0.5.2/specification/sections/01-flag-evaluation.md#1-flag-evaluation-api"/>
public sealed class Api
public sealed class Api : IEventBus
{
private EvaluationContext _evaluationContext = EvaluationContext.Empty;
private readonly ProviderRepository _repository = new ProviderRepository();
Expand All @@ -22,6 +23,8 @@ public sealed class Api
/// The reader/writer locks are not disposed because the singleton instance should never be disposed.
private readonly ReaderWriterLockSlim _evaluationContextLock = new ReaderWriterLockSlim();

internal readonly EventExecutor EventExecutor = new EventExecutor();


/// <summary>
/// Singleton instance of Api
Expand All @@ -42,6 +45,7 @@ private Api() { }
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
public async Task SetProvider(FeatureProvider featureProvider)
{
this.EventExecutor.RegisterDefaultFeatureProvider(featureProvider);
await this._repository.SetProvider(featureProvider, this.GetContext()).ConfigureAwait(false);
}

Expand All @@ -54,6 +58,7 @@ public async Task SetProvider(FeatureProvider featureProvider)
/// <param name="featureProvider">Implementation of <see cref="FeatureProvider"/></param>
public async Task SetProvider(string clientName, FeatureProvider featureProvider)
{
this.EventExecutor.RegisterClientFeatureProvider(clientName, featureProvider);
await this._repository.SetProvider(clientName, featureProvider, this.GetContext()).ConfigureAwait(false);
}

Expand Down Expand Up @@ -201,6 +206,28 @@ public EvaluationContext GetContext()
public async Task Shutdown()
{
await this._repository.Shutdown().ConfigureAwait(false);
await this.EventExecutor.Shutdown().ConfigureAwait(false);
}

/// <inheritdoc />
public void AddHandler(ProviderEventTypes type, EventHandlerDelegate handler)
{
this.EventExecutor.AddApiLevelHandler(type, handler);
}

/// <inheritdoc />
public void RemoveHandler(ProviderEventTypes type, EventHandlerDelegate handler)
{
this.EventExecutor.RemoveApiLevelHandler(type, handler);
}

/// <summary>
/// Sets the logger for the API
/// </summary>
/// <param name="logger">The logger to be used</param>
public void SetLogger(ILogger logger)
{
this.EventExecutor.Logger = logger;
}
}
}
25 changes: 25 additions & 0 deletions src/OpenFeature/Constant/EventType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace OpenFeature.Constant
{
/// <summary>
/// The ProviderEventTypes enum represents the available event types of a provider.
/// </summary>
public enum ProviderEventTypes
{
/// <summary>
/// ProviderReady should be emitted by a provider upon completing its initialisation.
/// </summary>
ProviderReady,
/// <summary>
/// ProviderError should be emitted by a provider upon encountering an error.
/// </summary>
ProviderError,
/// <summary>
/// ProviderConfigurationChanged should be emitted by a provider when a flag configuration has been changed.
/// </summary>
ProviderConfigurationChanged,
/// <summary>
/// ProviderStale should be emitted by a provider when it goes into the stale state.
/// </summary>
ProviderStale
}
}
Loading

0 comments on commit f5fc1dd

Please sign in to comment.