Motivation
Provide a mechanism for continued delivery together with continued development.
Solution
Use trunk-based development and feature toggles.
Implementation
Before starting to work with a feature, we need to create a new feature flag in GitLab and disable it. After that, all development should be branched with a feature toggle. When the feature is developed and deployed we can enable a feature in GitLab for the environment and it will activate during runtime. After the next release, we delete a feature flag and clean up the code.
GitLab feature management
GitLab provides a built-in solution for feature flags where we can create, add strategies, and manage. GitLab uses unleash client(we can also use direct GitLab API) to work with features. We can install unleash NuGet package from here.
During development, I found two weird things
-
UnleashSettings
has a specific property for Environment, but the client works only when we set the GitLab environment name to AppName. - It is hard to understand where we should set Instance ID in
UnleashSettings
. In GitHub example - ProjectId, but property that work InstanceTag
var unleash = new DefaultUnleash(new UnleashSettings
{
AppName = "environment_name"
UnleashApi = new Uri("API URL in GitLab config"),
InstanceTag = "Instance ID in GitLab config",
SendMetricsInterval = null,
});
Environments
Important GitLab feature - environments. We deploy our solution to specific environments and we can activate/deactivate feature flags per environment.
Microsoft feature management
Unleash client is good enough, but we want to use
dotnet feature management, which has great integration with ASP.NET Core. To do that we should implement IFeatureDefinitionProvider
for GitLab
internal class GitLabFeatureProvider : IFeatureDefinitionProvider
{
private IUnleash unleash;
public GitLabFeatureProvider(IUnleash unleash)
=> this.unleash = unleash;
public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
{
await Task.CompletedTask;
foreach (var feature in this.unleash.FeatureToggles)
{
yield return CreateFeatureDefinition(feature.Name, feature.Enabled);
}
}
public Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
{
var active = unleash.IsEnabled(featureName);
return Task.FromResult(CreateFeatureDefinition(featureName, active));
}
private static FeatureDefinition CreateFeatureDefinition(string name, bool active)
{
return new FeatureDefinition
{
Name = name,
EnabledFor = active
? new[] { new FeatureFilterConfiguration { Name = "AlwaysOn" } }
: Array.Empty<FeatureFilterConfiguration>()
};
}
}
Using feature management
In the end, we need to register feature management
.AddFeatureManagement()
.AddSingleton<IFeatureDefinitionProvider, GitLabFeatureProvider>();
inject IFeatureManager
into our solution and use it
await feature.IsEnabledAsync(nameof(FeatureFlags.myfeature))
Conclusions
- We manage our features during runtime using GitLab feature flags per environment
- We can rollback feature during runtime
- We develop and deploy without impact to the system and end users.
Top comments (0)