Brighter icon indicating copy to clipboard operation
Brighter copied to clipboard

[Feature] First class support for Azure App Configuration Feature Management

Open Jonny-Freemarket opened this issue 2 months ago • 1 comments

Azure App Configuration has a Feature Manager that allows you to toggle features on and off. These feature switch values can refreshed as part of the HTTP pipeline in ASP.NET Core, or by explicitly refreshing them with the Azure App Configuration SDK.

Brighter has the IAmAFeatureSwitchRegistry that allows for the Azure App Configuration Feature Manager to be used to control whether a Brighter handler runs or not in the pipeline. The basic building blocks of this are:

Configure the Azure App Configuration SDK to use Feature Manager

builder.Configuration
          .AddAzureAppConfiguration(options =>
          {
             ...
              
              options.Connect(..)
                  ...
                  .UseFeatureFlags(opts => opts.Select("YourServiceName_*", "flags"))
                  .ConfigureRefresh(refresh => refresh.Register("FeatureManagement").SetCacheExpiration(TimeSpan.FromSeconds(30)));
              
              builder.Services.AddSingleton(options.GetRefresher());
          });

Implement concrete IAmAFeatureSwitchRegistry

public class AppConfigurationFeatureSwitchRegistry : IAmAFeatureSwitchRegistry
{
    private readonly IFeatureManagerSnapshot _featureManager;
    private readonly IConfigurationRefresher _configurationRefresher;

    public AppConfigurationFeatureSwitchRegistry(IFeatureManagerSnapshot featureManager, IConfigurationRefresher configurationRefresher)
    {
        _featureManager = featureManager;
        _configurationRefresher = configurationRefresher;
    }

    public FeatureSwitchStatus StatusOf(Type handler)
    {
        ...
        // Removed for brevity
        ...
    }

    // As Brighter handlers not always part of an HTTP pipeline we have to force a refresh of the feature flags
    private async Task<bool?> CheckFeatureFlags(string feature)
    {
        await _configurationRefresher.TryRefreshAsync();

        await foreach (var featureName in _featureManager.GetFeatureNamesAsync())
        {
            if (featureName == feature)
                return await _featureManager.IsEnabledAsync(feature);
        }

        return null;
    }

    // If I feature switch is not found, but the handler is decorated with the attribute then
    // then the handler will still be allowed to run
    public MissingConfigStrategy MissingConfigStrategy { get; set; } = MissingConfigStrategy.SilentOn;
}

Decorating Brighter Handler with Feature Switch

...
[FeatureSwitchAsync(typeof(YourCommandHandler), FeatureSwitchStatus.Config, 0)]
public override async Task<YourCommand> HandleAsync(YourCommandmessage, CancellationToken cancellationToken = new CancellationToken())
{
    ...
}
...

Proposed Change

  • We wrap up the required plumbing into extension methods / bundled classes
  • Introduce a new Brighter attribute to use instead of the [FeatureSwitchAsync(..)] attribute, something like:

[AppConfigurationFeatureSwitchAsync(nameof(YourCommandHandler), 0)]

Jonny-Freemarket avatar Apr 11 '24 17:04 Jonny-Freemarket