As soon as a web project reaches a certain level of complexity, managing the deployment of new features becomes a crucial issue. In the past, developers sometimes worked for weeks on isolated features before merging their work for a new release. Today, deployment cycles are much more frequent; it’s not uncommon for teams working on distinct features to deploy their code several times a day within the same project.
While this approach to continuous integration and deployment allows for faster innovation, it also poses concrete challenges: how can multiple developers work simultaneously without interfering with one another? How can we avoid delivering incomplete features in production? How can we segment features for different user groups, such as QA teams or a subset of users?
Feature flags address these issues by separating the deployment of code from the activation of its features.
Introduction to Feature Flags
Feature flags are simple booleans that allow you to enable or disable a feature. How do they address the challenges raised in the introduction?
By developing features behind feature flags, we ensure that only users meeting a specific set of conditions can see them. As long as the code for these features doesn’t break the main branch, it’s possible to continue pushing code regularly and transparently without disrupting other developers or breaking the production environment with unfinished features.
This approach allows for a trunk-based development strategy. Instead of creating long-lived branches for each new feature, trunk-based development encourages the creation of short-lived branches that can be regularly integrated into the main branch. The code in the main branch should always be buildable by any other developer, whether or not they are working on the same feature as you.
By regularly merging their code, developers can address merge issues early and encourage communication among team members. When two features cause too many problems during merging, it’s possible to redefine their scope, merge them, or break them down into smaller tasks to avoid the dreaded Merge Hell at the end of a sprint.
Use Cases for Feature Flags
By adopting feature flags, it is possible to manage a project and manually activate or deactivate features according to a set of defined rules. Use cases include:
- Activating features visible only to developers
- Activating features in production for QA teams
- Activating features for a specific client
- Deactivating features based on geographic, age, consent, etc., rules
- Creating A/B tests for a subset of users
- Gradual deployment of features, first for a test subset, then for half of your users, and finally for all
- Easing rollback of features for technical, managerial, or legal reasons
As you can see, implementing a feature flag management system opens up new possibilities for both developers and product teams.
Managing Feature Flags with an External Tool
While it may be tempting to develop your own feature flag management tool—after all, they are just booleans—it is generally better to rely on an external service.
The one I personally use is LaunchDarkly, one of the leaders in the market.
With this tool, you can create contexts that correspond to a categorization of your users. By default, the user
context is created, but you can also imagine contexts for devices
, locations
, or any other variation or combination suited to your needs.
Rather than defining specific feature flags in your code, such as $user->canEnableDarkMode()
, you delegate this logic to the external tool by providing it with the necessary information to construct user segments.
A Concrete Example
Let's consider a concrete case. You are developing a new social network. A new regulation requires that users born in France between 2002 and 2003 who created an account in 2023 must accept your new terms of service before accessing your application.
Here’s how this implementation might look without using feature flags:
// Pseudo-code
$user = get_user();
$account_creation_time = $user->getCreatedTime();
$birth_country = $user->get('birth_country');
$birth_date = $user->get('birth_date');
if (
(new DateTime($account_creation_time))->format('Y') == "2023" &&
in_array((new DateTime($birth_date))->format('Y'), ['2002', '2003']) &&
$birth_country == "France"
) {
show_legal_stuff();
}
This code works, but it is difficult to audit if it is scattered throughout your codebase. When the regulation is inevitably modified six months later, you’ll need to ensure that you change this condition everywhere it is used and then test that it works correctly.
Of course, you could create a service for this and organize your code so that you only have to modify this logic in one place, but feature flags offer a much more elegant solution, in my opinion:
// Pseudo-code
$user = get_user();
// Somewhere in your application, you send the user attributes to the feature flags handler
$feature_flags_context = FeatureFlagContext::builder('user-' . $user->id())
->type('user')
->attributes([
'created_at' => $user->getCreatedTime(),
'birth_country' => $user->get('birth_country'),
'birth_date' => $user->get('birth_date'),
])
->build();
// ...
// Then, when needed, you check if the user has the feature flag
if ($feature_flags_client->hasFeatureFlag('show-legal-stuff', $feature_flags_context)) {
show_legal_stuff();
}
In the management interface of your feature flags, you can then define the same set of rules as in the first example, thus completely decoupling this functionality from its activation rules.
If a developer wants to test this feature, they can easily enable it for their account. If the regulation changes, it becomes trivial for a non-technical person in your organization to modify the activation rules.
Moreover, if your project has multiple services that communicate with each other, whether it has an API, a website, a mobile app, or any other medium, a centralized base allows you to share functionalities among different platforms without duplicating the logic for their activation.
Integrating LaunchDarkly with Drupal
To facilitate the integration of LaunchDarkly with Drupal, I developed a module that you can find on my GitHub at this address.
This module exposes a Twig function has_feature_flag()
to enable or disable features directly in your templates, as well as a PHP service launchdarkly.service
for server-side usage.
Here’s an example of usage on the PHP side:
$launchDarklyService = \Drupal::service('launchdarkly.service');
$flagStatus = $launchDarklyService->hasFeatureFlag('my-feature-flag', false);
if ($flagStatus) {
// Feature flag is on
} else {
// Feature flag is off
}
And an example of usage on the Twig side:
{% if has_feature_flag('my-feature-flag') %}
<p>This feature is enabled!</p>
{% else %}
<p>This feature is disabled.</p>
{% endif %}
Like the other modules I’ve shared on this site, this one aims to be as minimalistic as possible to serve as a base and can be adapted to your own needs.
It mainly consists of three files:
- SettingsForm.php : creates an admin form to enter a key for the LaunchDarkly SDK
- LaunchDarklyService.php : exposes a service that sends user information to LaunchDarkly upon construction and includes a method to check if a flag is enabled or not
- LaunchDarklyTwigExtension.php : a Twig extension that exposes the
hasFeatureFlag
method from the service for Twig usage.
If you want to integrate a feature flag system into your project, feel free to use this project as a working base.
Conclusion/Summary
Feature flags are an excellent way to separate the implementation of features from their activation in an application. They allow you to create distinctions between your users, your internal teams, and your environments, thus facilitating a trunk-based development approach without the risk of breaking your production environment.
Preferring to rely on external services rather than developing a custom solution, I chose to adopt LaunchDarkly, which perfectly fits my use case.
To facilitate its integration with Drupal, I developed a minimalist module exposing the necessary functions for its operation in both Twig and PHP.