dapr / dotnet-sdk

Dapr SDK for .NET
Apache License 2.0
1.11k stars 331 forks source link

Azure App Configuration - Feature Flags with Dapr #1295

Open tomas-blanarik opened 3 months ago

tomas-blanarik commented 3 months ago

Hello, we're using FeatureManagement library from Microsoft and we wanted to use it altogether with Azure App Configuration. However, we use Dapr .NET SDK for all 3rd party components (service bus, key vault, ...) and we're also using .NET extension method AddDaprConfigurationStore to register Azure App Configuration. However, the feature flags are not loaded also they're not available in IConfiguration. Any ideas ?

philliphoff commented 3 months ago

@tomas-blanarik Can you provide any sort of repro project or redacted snippet of startup code? Are the configuration values (used for the feature flags) specified in the AddDaprConfigurationStore() method but still not added to the IConfiguration?

tomas-blanarik commented 3 months ago

So simply my method for registering configuration sources is something like this

public static void AddCustomConfiguration(this WebApplicationBuilder builder)
    {
        if (builder.Environment.IsLocal())
        {
            builder.Configuration.AddJsonFile("features.json", optional: true, reloadOnChange: true);
        }

        builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
        builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
        builder.Configuration.AddEnvironmentVariables();

        if (!builder.Environment.IsLocal())
        {
            string[] delimiters = ["--", ":", "__"];
            var logger = builder.Services.GetRequiredService<ILoggerFactory>().CreateLogger("DI");

            try
            {
                var daprClient = new DaprClientBuilder().Build();
                builder.Configuration.AddDaprSecretStore(
                    CoreConstants.Dapr.Components.SecretStorage, // resolves to "keyvault"
                    daprClient,
                    delimiters,
                    TimeSpan.FromSeconds(60));

                logger.LogInformation("Successfully registered dapr Secret Store to IConfiguration");

                builder.Configuration.AddDaprConfigurationStore(
                    CoreConstants.Dapr.Components.ConfigurationStorage, // resolves to "config"
                    [], // so I want to load everything, but I am starting to sense the problem that I need to be very specific here
                    daprClient,
                    TimeSpan.FromSeconds(60));

                logger.LogInformation("Successfully registered dapr Configuration Store to IConfiguration");
            }
#pragma warning disable S2139
            catch (Exception e)
#pragma warning restore S2139
            {
                logger.LogError(e, "Cannot register dapr secret store to the configuration: {Message}", e.Message);
                Exception? inner = e.InnerException;
                while (inner is not null)
                {
                    logger.LogError(inner, "Inner exception: {Message}", inner.Message);
                    inner = inner.InnerException;
                }

                throw;
            }
        }
    }
tomas-blanarik commented 2 months ago

Ok my bad, I misread the documentation or misunderstood it in some way.

The correct way of using App Configuration was to explicitly specify keys to be loaded, for example .appconfig.featureflag/SomeFlagName and this worked. Also if you have labels adding metadata is needed (as also stated in documentation). Something like this new Dictionary<string, string> { { "label", "my-label" } }

Then I saw loading these values in console log which indicates that it's actually working :)

tomas-blanarik commented 2 months ago

However, the problem with loading values from App Configuration doesn't end there. If you want to use FeatureFlags you need to transform these into FeatureManagement section after they're loaded.

philliphoff commented 2 months ago

However, the problem with loading values from App Configuration doesn't end there. If you want to use FeatureFlags you need to transform these into FeatureManagement section after they're loaded.

@tomas-blanarik There's a new-ish feature of the SDK (SecretKey)which allows you to transform the keys of a secret store when adding them to the configuration. I'm not familiar enough with FeatureFlags to know whether that offers enough flexibility to automatically do what you need.

jypa-github commented 2 weeks ago

@tomas-blanarik Thank you so much for the information. I would really appreciate if you could help me out with how to parse the feature flag object. Based on your comment, I have tried loading feature flag with .appconfig.featureflag/SomeFlagName although I could not parse it to Dictionary<string, string> to see the value of enabled flag.

See the below exception when I tried to load with bool type, I know this is not the proper way of loading feature flag although just to show you the actual feature flag object I have used "bool"

2024-08-27 06:21:16 Unhandled exception. System.InvalidOperationException: Failed to convert configuration value at '.appconfig.featureflag/enableFocusOrderTab' to type 'System.Boolean'. 2024-08-27 06:21:16 ---> System.FormatException: {"id":"enableFocusOrderTab","description":"Enable Focus Order Tab","enabled":true,"conditions":{"client_filters":[]}} is not a valid value for Boolean. 2024-08-27 06:21:16 ---> System.FormatException: String '{"id":"enableFocusOrderTab","description":"Enable Focus Order Tab","enabled":true,"conditions":{"client_filters":[]}}' was not recognized as a valid Boolean.

Please guide me how to transform featureFlags config into FeatureManagement section.

Much appreciated!!