Publié le

Intégrer des feature flags à Drupal avec LaunchDarkly

Dès qu'un projet web atteint un certain niveau de complexité, la gestion du déploiement de nouvelles fonctionnalités devient un enjeu crucial. Dans le passé, les développeurs travaillaient parfois pendant des semaines sur des fonctionnalités isolées, avant de fusionner leur travail pour la sortie d'une nouvelle version. Aujourd’hui, les cycles de déploiement sont beaucoup plus rapprochés ; il n'est pas rare que des équipes travaillant sur des fonctionnalités distinctes déploient leur code plusieurs fois par jour sur un même projet.

Si cette approche d’intégration et de déploiement continus permet d’innover plus rapidement, elle pose des problèmes concrets : comment plusieurs développeurs peuvent-ils travailler simultanément sans se gêner mutuellement ? Comment éviter de livrer des fonctionnalités incomplètes en production ? Comment segmenter les fonctionnalités pour différents groupes d’utilisateurs, comme les équipes de QA ou une partie de ses utilisateurs ?

Les feature flags répondent à ces problématiques en séparant le déploiement du code de l’activation de ses fonctionnalités.

Présentation des feature flags

Les feature flags sont de simples booléens permettant d'activer ou de désactiver une fonctionnalité. Comment répondent-ils aux problématiques soulevées en introduction ?

En développant des fonctionnalités derrière des feature flags, on s'assure que seuls les utilisateurs répondant à un ensemble de conditions puissent les voir. Tant que le code de ces fonctionnalités ne casse pas la branche principale, il est donc possible de continuer à pousser du code de manière régulière et transparente sans gêner ses autres collègues développeurs ni casser l'environnement de production avec des fonctionnalités non finalisées.

Cette approche permet donc d'adopter une approche de développement trunk-based. Plutôt que de créer des branches spécifiques pour chaque nouvelle fonctionnalité (long-lived), le trunk-based development encourage la création de petites branches (short-lived) qui peuvent être intégrées régulièrement à la branche principale. Le code de la branche principale doit toujours être buildable par n'importe quel autre développeur, qu'il travaille sur la même fonctionnalité que vous ou non.

En fusionnant régulièrement son code, on règle ainsi les soucis de merge en amont et on encourage la communication entre les développeurs. Lorsque deux fonctionnalités posent trop de problèmes au moment de leur fusion, il est possible de retravailler leur périmètre, de les fusionner ou de les diviser en tâches plus granulaires pour éviter le Merge Hell tant redouté en fin de sprint.

Cas d'utilisation des feature flags

En adoptant les feature flags, il est possible de piloter un projet et d'activer ou désactiver des fonctionnalités manuellement ou selon un ensemble de règles définies. Les cas d'utilisation incluent :

  • l'activation de fonctionnalités visibles uniquement pour les développeurs
  • l'activation de fonctionnalités en production pour les équipes de QA
  • l'activation de fonctionnalités pour un client spécifique
  • la désactivation de fonctionnalités selon des règles géographiques, d'âge, de consentement, etc.
  • la création d'A/B tests pour un sous-ensemble de vos utilisateurs
  • le déploiement graduel de fonctionnalités, d'abord pour un sous-ensemble de test, puis pour la moitié de vos utilisateurs, puis pour l'ensemble
  • la rollback facilité de fonctionnalités pour des raisons techniques, managériales ou légales

Vous l'avez compris, implémenter un système de gestion des feature flags ouvre un nouveau champ des possibles, aussi bien pour les développeurs que pour les équipes produit.

Piloter ses feature flags avec un outil externe

S'il peut être tentant de développer son propre outil de gestion des feature flags – ce ne sont que des booléens après tout –, il est généralement préférable de se reposer sur un service externe.

Celui que j'utilise personnellement est LaunchDarkly, un des leaders du marché.

Avec cet outil, il est possible de créer des contextes correspondant à une catégorisation de vos utilisateurs. Par défaut, le contexte user est créé, mais vous pouvez également imaginer des contextes device, location ou toute autre variation ou combinaison adaptée à vis besoins.

Plutôt que de définir des feature flags spécifiques côté code du type $user->canEnableDarkMode() par exemple, on délègue cette logique à l'outil externe en lui fournissant les informations nécessaires à la construction de segments utilisateurs.


Prenons un cas concret. Vous développez un nouveau réseau social. Une nouvelle réglementation impose que vos utilisateurs nées en France entre 2002 et 2003 ayant créé un compte en 2023 acceptent vos nouvelles conditions générales avant d'accéder à votre application.

Voici à quoi cette implémentation pourrait ressembler sans l'utilisation des 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();
}

Ce code fonctionne, mais il est difficilement auditable si celui-ci est parsemé dans toute votre base de code. Lorsque la réglementation sera assurément modifiée à nouveau 6 mois plus tard, il faudra s'assurer de modifier cette condition partout où elle est utilisée puis de tester qu'elle fonctionne correctement.

Vous pourriez bien sûr créer un service pour cela et organiser votre code pour n'avoir à modifier ce fonctionnement qu'à un seul endroit, mais les feature flags offrent une solution bien plus élégante selon moi.

// 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();
}

Dans l'interface de gestion de vos feature flags, vous pouvez alors définir le même ensemble de règles que pour le premier exemple permettant ainsi de découpler totalement cette fonctionnalité de ses règles d'activation.

Si un développeur souhaite tester cette fonctionnalité, il peut facilement l'activer pour son compte. Si la réglementation change, il devient trivial de modifier les règles d'activation par une personne non technique de votre organisation.

De plus, si votre projet comporte de multiples services qui communiquent entre eux, qu'il possède une API, un site web, une application mobile ou tout autre medium, une base centralisée permet de partager ses fonctionnalités entre les différents supports sans avoir à dupliquer la logique de leur activation.

Intégrer LaunchDarkly avec Drupal

Pour faciliter l'intégration de LaunchDarkly avec Drupal, j'ai développé un module que vous pouvez retrouver sur mon GitHub à cette adresse.

Ce module expose une fonction Twig has_feature_flag() pour activer ou désactiver des fonctionnalités directement dans vos templates, ainsi qu’un service PHP launchdarkly.service pour le faire côté serveur.

Voici un exemple d'utilisation côté PHP :

$launchDarklyService = \Drupal::service('launchdarkly.service');
$flagStatus = $launchDarklyService->hasFeatureFlag('my-feature-flag', false);

if ($flagStatus) {
  // Feature flag is on
} else {
  // Feature flag is off
}

et un exemple d'utilisation côté Twig :

{% if has_feature_flag('my-feature-flag') %}
  <p>This feature is enabled!</p>
{% else %}
  <p>This feature is disabled.</p>
{% endif %}

Comme les autres modules que j'ai partagés sur ce site, celui-ci se veut le plus minimaliste possible pour servir de base et l'adapter à vos propres besoins.

Il se compose principalement de 3 fichiers :

  • SettingsForm.php : il créé un formulaire d'administration permettant de renseigner une clef pour le SDK de LaunchDarkly
  • LaunchDarklyService.php : il expose un service qui se charge à sa construction d'envoyer à LaunchDarkly les informations de l'utilisateur et une méthode permettant de vérifier si un flag est activé ou non
  • LaunchDarklyTwigExtension.php : une extension twig exposant la méthode hasFeatureFlag du service côté Twig.

Si vous souhaitez intégrer un système de feature flag à votre projet, n'hésitez pas à vous servir de ce projet comme base de travail.

Conclusion/résumé

Les feature flags sont un excellent moyen de séparer l'implémentation des fonctionnalités de leur activation dans une application. Grâce à elles, il est possible de créer des distinctions entre vos utilisateurs, vos équipes internes et vos environnement facilitant ainsi une approche trunk-based du développement sans risque de casser votre environnement de production.

Préférant me baser sur des services externes plutôt que de développer une solution sur-mesure, j'ai choisi d'adopter LaunchDarkly qui répond parfaitement à mon cas d'utilisation.

Afin de faciliter son intégration avec Drupal, j'ai développé un module minimaliste exposant les fonctions nécessaires à son fonctionnement côté Twig et PHP.