Using feature flags in Azure Function and Durable Functions with Azure App Configuration

Well, well, well, you are looking to up your game! Then let’s do this. Let’s discuss feature flags, but first what are feature flags ?

Well, feature flags are a technique which enables you to be able to influence how your application works actually touching the code, but don’t take my word for it, check what Martin Fowler has to say about them.

We have already looked at this in some way in Inject configuration values in Azure Functions. In that article you can see how you can load environment variables in you application. Based on those you can influence how your application behaves. But that has a downside though, every time you change the app config in you azure function, the application gets restarted, not ideal… and if this wasn’t enough, this is restricted to the current application, so as you might imagine, you can not read the contents of an app config of a different application, bummer…

Why bummer ? Well, aren’t we living the the age of microservices ? Don’t we wont to have a gazillion of independently deploy-able, self healing, reactive, owners of their own data, breaker of chains nano-services ?

Well, in this day and age, when we are doing web-scale, most of the time, some feature would span across multiple micro services and UIs, so in this case, using the local app config in each of them would be quite tiresome and quite error prone. We need a better solution for this. Lucky there are quite a number of solutions out there, one of which is also in azure ( how convenient ), and we will take a quick look at it. The name is *drum roll* Azure App Configuration !!!111

Yeah, shocking … well, this is what were will be looking at today. Well, you at least. So, in order to get started please visit the official quick start guide, and after you finished going through it, I’ll eagerly wait for you back here to go through a quick and nice example. Also, at the time of writing this, the documentation was a bit out of date, and I had to do some things here and there to fix it, things that will be shown below.

The Code

Well now that you went through the quickstart ( hopefully ), you should have your own app configuration endpoint as well the connection string in you environment variables.

Now, a few more things that we need to add, as you might notice in the quick start, they are using the annoyingly static type of functions, we will use the more class based and DI / IOC way of doing things.

Also, as usually you can find the code on github.

Okay, in order to get the ball rolling, we need to initially setup the Startup file as such:

[assembly: FunctionsStartup(typeof(Startup))]
namespace FunWithFlags
{
    public class Startup : FunctionsStartup
        {
            private IConfiguration configuration;
            public override void Configure(IFunctionsHostBuilder builder)
            {
                this.configuration = new ConfigurationBuilder()
                    .AddEnvironmentVariables()
                    .AddAzureAppConfiguration(options =>
                    {
                        options.Connect(Environment.GetEnvironmentVariable("ConnectionString"))
                            .UseFeatureFlags();
                    }).Build();
                ServiceRegistrations.ConfigureServices(builder.Services, this.configuration);
            }
        }
}

Here we setup the usual start-up part, then we need to setup the ServiceRegistrations class

    public static class ServiceRegistrations
    {
        public static void ConfigureServices(IServiceCollection builderServices, IConfiguration configuration)
        {
            builderServices.AddFeatureFlags(configuration);
        }
        private static IServiceCollection AddFeatureFlags(this IServiceCollection serviceCollection, IConfiguration configuration)
        {
            serviceCollection.AddSingleton<IConfiguration>(configuration).AddFeatureManagement();
            serviceCollection.AddAzureAppConfiguration();
            return serviceCollection;
        }
    }

So, the essential parts are highlighted, nothing new, just adapted to the DI style functions.

After setting the usual DI container we have the following function code:

    public class ShowShipFlag
    {
        private readonly IFeatureManager _featureManager;
        private readonly IConfigurationRefresher _refresher;
        public ShowShipFlag(IFeatureManager featureManager, IConfigurationRefresherProvider refresherProvider)
        {
            _featureManager = featureManager;
            _refresher = refresherProvider.Refreshers.First();
        }
        [FunctionName("ShowShipFlag")]
        public async Task<IActionResult> RunAsync(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
            HttpRequest req, ILogger log)
        {
            await _refresher.TryRefreshAsync();
            var shipFlag = "The India Company";
            var usePirateShip = await this._featureManager.IsEnabledAsync("pirate-flag");
            if (usePirateShip)
            {
                shipFlag = "Pirate";
            }
            return (ActionResult) new OkObjectResult($"The ship has a {shipFlag} flag ");
        }
    }

Now, looking back at this and at what you have achieved in the quick start, you might have noticed something new here, the ” refresher” bit, you know the one that is highlighted. This actually makes the magic happen, and forces the feature flags to update the value, otherwise, you use only the initial value that gets cached for the lifetime of the app. Now, what you need to keep in mind is that although you are attempting to refresh, this is done by default in a 30 sec caching window, so once you flip the switch, you might have to wait a bit until you get the desired result, but this could be changed to different values in the Startup file.

As you have seen this can be used inside an activity and can also be used inside an orchestrator

    public class ShipDefenseOrchestrator
    {
        private readonly IFeatureManager _featureManager;
        private readonly IConfigurationRefresher _refresher;
        public ShipDefenseOrchestrator(IFeatureManager featureManager, IConfigurationRefresherProvider refresherProvider)
        {
            _featureManager = featureManager;
            _refresher = refresherProvider.Refreshers.First();
        }
        [FunctionName(nameof(ShipDefenseOrchestrator))]
        public  async Task<List<string>> Run(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var actions = new List<string>();
            await this._refresher.TryRefreshAsync();
            var isParanoid = await this._featureManager.IsEnabledAsync("IsParanoid");
            var flag = await context.CallActivityAsync<string>(nameof(CheckOtherShipsFlag), new { });
            if (flag == "Pirate")
            {
                actions.Add(await context.CallActivityAsync<string>(nameof(PrepareDefensiveManeuvers), null));
                actions.Add(await context.CallActivityAsync<string>(nameof(FireCanons), null));
            }
            if (isParanoid)
            {
                actions.Add(await context.CallActivityAsync<string>(nameof(PrepareDefensiveManeuvers), null));
            }
            return actions;
        }
        [FunctionName(nameof(CheckOtherShipsFlag))]
        public async Task<string> CheckOtherShipsFlag([ActivityTrigger] object obj)
        {
            await _refresher.TryRefreshAsync();
            var shipFlag = "The West India Company";
            var usePirateShip = await this._featureManager.IsEnabledAsync("pirate-flag");
            if (usePirateShip)
            {
                shipFlag = "Pirate";
            }
            return shipFlag;
        }
        [FunctionName(nameof(PrepareDefensiveManeuvers))]
        public async Task<string> PrepareDefensiveManeuvers([ActivityTrigger] object obj)
        {
            return "To battle stations!";
        }
        [FunctionName(nameof(FireCanons))]
        public async Task<string> FireCanons([ActivityTrigger] object obj)
        {
            return "BOOM!";
        }
        [FunctionName(nameof(IsParanoid))]
        public async Task<bool> IsParanoid([ActivityTrigger] object unit)
        {
            await this._refresher.TryRefreshAsync();
            return await this._featureManager.IsEnabledAsync("IsParanoid");
        }
        [FunctionName("ShipDefenseOrchestrator_HttpStart")]
        public async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]
            HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            // Function input comes from the request content.
            string instanceId = await starter.StartNewAsync(nameof(ShipDefenseOrchestrator), null);
            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
            return starter.CreateCheckStatusResponse(req, instanceId);
        }
    }

Quite straight forward. Now, I know this is quite a simple example, but quite a powerful one. Just imagine you have multiple function apps and other apps working together and having a new functionality deployed, that could be enabled or disabled at a flip of a … well checkbox, sounds cool right ?

Also, even better, you could combine these feature flags with the technique described in Dynamically select orchestrators in Azure Durable Functions, and you could get quite spectacular results.

Also, in case you missed the link to github: https://github.com/laur3d/FunWithFlags

Now, if you need a few more examples on how you could use feature flags check this article: 4 ways of using Feature Flags like you actually know what your doing!

If you liked the content, consider joining the list to get notified 😉

Processing…
Success! You're on the list.

2 thoughts on “Using feature flags in Azure Function and Durable Functions with Azure App Configuration”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s