Feature Flags
To understand where feature flags can play a role in complementing our iterative and continuous delivery we need to reflect on a recent development work we undertook.
The previous image is a rough overview of our DevOps environment while we were working through “Increment 2” of our VRM application.
After the release of the MVP for the Vessel Registry Management application, the inception of Develop 2.1 Branch took place. The branch’s purpose was to encapsulate the features required for Increment 2, small vessel registry.
While the features for increment 2 were being developed on the Develop 2.1 Branch, development on the MVP Develop branch was still ongoing to support the production application. So developers would create a branch based on either the Develop or the Develop 2.1 branch and then keep both branches in sync.
Finally, when Increment 2 was released, Develop 2.1 became the primary branch.
The previously outlined approach is based on a branching strategy called feature branching. While this is a valid and common technique, it is not suited for long running features that extend more than a day or two. This is because they start to create merge problems, and the isolation of code or in our case the two branches would not allow for the detection of problems early on. It also strongly discourages refactoring which leads to code health deterioration.
Feature Flags
At it’s core, feature flag is a process in software development that allows us to enable or disable a feature remotely without deploying code changes. Feature flags work by isolating a work-in-progress code behind “flags“ and the functionality associated with the code would not be accessible until the feature is enabled. By following the 12 Factor App methodology, and more specifically the 3rd factor(Config), feature enablement flags can be abstracted out to config files, which can be controlled dynamically at runtime.
For the rest of this section, I will be using the Vessel Registry Management Project as the example project. You can find the demo here.
In comparison to what we saw in feature branching, feature flags work by placing a runtime controlled boolean logic within the codebase itself instead of creating a separate branch.
Toggle Router
Previously, we created a feature branch, what if instead of creating a new branch we introduce a toggle router and a flag.
Toggle Router is just a fancy name to dynamically enable/disable a flag.
Looking at the previous diagram, let’s assume Developer 1 is doing some code refactoring as part of the work related to the feature.
(1) Let’s give the feature a name
Groups
Recall our talk about earlier about the 3rd factor(Config) in the 12 Factor App methodology? Let’s add our feature flag to our app settings config file.
Set the Groups flag to false, to indicate, this feature is not active yet.
"Groups": false
The section of code we want to modify is the navigation associated with service requests page. Navigate to the ServiceRequests.razor.cs page, and inject the application configuration properties.
[Inject] IConfiguration Configuration
Instead of combining all requests into a single page, what we aim to achieve with this refactoring, is to separate each request to use it’s own page based on the request type. Find the OnTableRowClick method within the ServiceRequestBase class.
Modify the code in the method to match the following:
protected void OnTableRowClick(VesselRegistryViewModel model)
{
if (Configuration.GetValue<bool>("Groups"))
{
}
else
{
navigationManager.NavigateTo($"{LanguageCode}/servicerequest/summary/{model.WorkItemId}");
}
}
We used the inject configuration property to get the value from the appsettings.Development.json file, and added an if/else statement block to determine which code to execute based on the Groups flag. Running the code now, would still yield the previous behavior. The flag is set to false.
Add a new Blazor page called FirstRegistry and add/modify the page directive. We will also take those values and output them to the page body. In the path we set the value 022, which is the request type id of a first registry request. With this modification all first registry requests would be routed to this page.
Navigate back to the ServiceRequestBase class and modify the OnTableRowClick method with the new navigation path.
Again, if we run the code as it is at this moment nothing would change, because the Groups flag is still set to true. Let’s change the value of the Groups flag to true.
Run the application and navigate to the Request Board click on any First Registry Request. You will be navigated to the newly created page, and the values will be displayed as shown in the previous images.
Azure App Configuration
Now that works, but the problem here is everyone will receive this change. So how do we isolate this change to only certain users?
This is where we introduce conditional feature flags or feature filters. With feature filters we can for example:
Enable a feature flag based on a percentage (PercentageFilter)
Enable a feature for specified duration (TimeWindowFilter)
Enable a feature flag for a specified user or groups of users (TargetingFilter)
To allow for that kind of filter we can either:
Build our own Feature Management system
Use the Azure App Configuration Feature Management (We will use the Azure App Configuration Feature Management)
In the Azure portal search for the App Configuration resource.
Create a new App Configuration, I will call it vr-vrm-featureflag-example, you can use your own naming convention, I selected the free tier.
In the Networking tab, choose the appropriate access option for you circumstance. I left it as automatic for this walk through.
The Tags tab can be left empty or you can enter the tags appropriate for your team. Review the changes you made and create the App Configuration.
You will see the newly created App Configuration. Click on the newly created vr-vrm-featureflag-example, and navigate to the Feature Manager section then click on the Add button.
On the Add page:
Check the Enable feature flag
Enter the Feature flag name, I entered Groups. You can fill the rest of the information under the Details section or leave them empty
Check the Use feature filter
Select the Targeting option
Set the Default Percentage to 0 if it is not already set
You enter a group name or specify an individual user. I am going to enter my email under the Users, so that the feature is only displayed for me.
Click Apply
Before we leave the App Configuration resource, navigate to the Settings / Access keys and click on the Read-only keys tab and copy the Connection string.
Targeting Filters
Use the secrets manager to store the App Configuration connection string for local development. In the appsettings.Development.json, I am going to create a key called AppConfig under the connection strings section, which I will use to inject the connection string from the secrets manager we copied earlier.
Remove the “Groups” flag we created earlier from our appsettings.Development.json config file, we are going to use the Azure App Configuration to get that value.
Add the Microsoft.FeatureManagement.AspNetCore package
And the Microsoft.Azure.AppConfiguration.AspNetCore
Under the Utilities folder in the Vessel Registry Management Project, add a class called TargetingContextAccessor. The targeting filter will use this class during runtime to determine whether the user has access or not.
// Source: Roll out features to targeted audiences in an ASP.NET Core app - Azure App Configuration
Register the targeting filter in the ConfigureServices method of the Startup.cs class:
In the Program.cs file register the configuration provider for the App Configuration with the .NET Core Configuration API.
Add a feature flag static class called FeatureFlags to the Vessel Registry Management / Constants folder
Navigate back to the ServiceRequests.razor.cs class and replace the previously injected IConfiguration with the IFeatureManager interface.
Replace
With
Refactor the OnTableRowClick with the following
Run the application and navigate to the service request board and select any request, and you should be taken to the newly added page again.