Les Patterns des Grands du Web - Feature Flipping

Description

Le pattern « feature flipping » permet d’activer et désactiver des fonctionnalités directement en production, sans re-livraison de code.

Plusieurs termes sont utilisés par les grands du web : Flickr et Etsy utilisent des « feature flags », Facebook des « gatekeepers », Forrst des « feature buckets », des « features bits » chez Lyris inc., alors que Martin Fowler opte pour des « feature toggles ».

Bref, chacun le nomme et l’implémente à sa manière mais dans l’idée toutes ces techniques rejoignent les mêmes objectifs. Dans cet article, nous utiliserons le terme « feature flipping ».  Mis en œuvre avec succès sur notre solution de store privé Appaloosa, cette technique a apporté de nombreux bénéfices pour quelques inconvénients.

Mise en œuvre

Le mécanisme est très simple, il suffit de conditionner l’exécution du code de la fonctionnalité de la manière suivante :

if Feature.is_enabled(‘new_feature’)
# do something new
else
# do same as before
end

L’implémentation de la fonction « is_enabled » va par exemple regarder un fichier de configuration ou interroger une base de données pour savoir si la fonctionnalité est activée ou non.

Une console d’administration est alors nécessaire pour configurer l’état des différents « flags », à chaud sur les différents environnements.

Déployer en continu

Une des premiers avantages de pouvoir activer et désactiver des fonctionnalités à chaud est de livrer en continu l’application en production. En effet, les organisations qui mettent en place un processus de livraison continue sont vite confrontées à un problème : comment commiter fréquemment sur le référentiel de source tout en garantissant la stabilité de l’application, toujours prête pour la production ? Dans le cas de développements de fonctionnalités qui ne peuvent être terminés en moins d’une journée, commiter la fonctionnalité seulement lorsqu’elle est terminée (au bout de quelques jours) est contraire aux bonnes pratiques de développement en intégration continue. En effet, plus les commits sont espacés, plus les merges sont complexes et risqués, et les possibilités de refactoring transverse limitées. Face à cette contrainte, deux possibilités : « feature branching » ou « feature flipping ». En d’autres termes, brancher via l’outil de gestion de configuration ou brancher dans le code. Le débat entre ces deux approches est passionné, vous pouvez trouver quelques avis ici : http://jamesmckay.net/2011/07/why-does-martin-fowler-not-understand-feature-branches/

Le feature flipping permet au développeur de coder à l’intérieur de son « if », et ainsi commiter un code non terminé, non fonctionnel, tant que le code compile et que ses tests passent. Les autres développeurs peuvent récupérer les modifications sans problèmes tant qu’ils n’activent pas la fonctionnalité en cours de développement. Et le code peut alors être déployé en production puisque là encore, la fonctionnalité ne sera pas activée. C’est là tout l’intérêt de la démarche : ne pas conditionner le déploiement du code en production par la complétude de toutes les fonctionnalités en cours de développement. Une fois la fonctionnalité terminée, elle peut être activée simplement en changeant l’état du flag sur la console d’administration. Cela présente un autre avantage, on peut par exemple activer la fonctionnalité de manière synchronisée avec une campagne marketing, on limite alors les risques d’erreurs suite à une MEP non maitrisée le jour J.

Sécuriser les déploiements

Un des gains les plus intéressant du pattern est qu’il permet de sécuriser les déploiements. En effet, de la même manière qu’on peut activer une fonctionnalité à chaud en un clic, la désactivation est tout aussi simple, permettant d’éviter un processus de « rollback » long et risqué pour revenir à la version N-1 du système.

On peut donc très rapidement annuler l’activation d’une fonctionnalité si les tests en production ne sont pas concluants ou bien si les retours des premiers utilisateurs sont négatifs.

Tout n’est pas si simple malheureusement, il faut faire attention aux données et s'assurer que le modèle soit compatible avec et sans la fonctionnalité activée (Voir plus loin la partie « Limites et contraintes > Modifications lourdes »).

Expérimenter pour améliorer le produit

Une évolution naturelle du feature flipping est d’être capable d’activer/désactiver des fonctionnalités pour des sous-populations. Ceci permet alors d’activer une fonctionnalité pour une sous-population, et en fonction de leurs retours, activer l fonctionnalité pour tous les utilisateurs ou bien revenir en arrière. Le code ressemblera alors à ceci :

if Feature.is_enabled_for(‘new_feature’, current_user)
# do something new
else
# do same as before
end

On peut alors utiliser le mécanisme pour tester la performance d’une fonctionnalité en modifiant une variable de son implémentation sur plusieurs sous-populations. La mesure des résultats permettra de déterminer l’implémentation la plus performante à retenir. Autrement dit, le feature flipping représente un outil idéal pour procéder à des tests A/B.

Offrir un produit customisable

Dans certains cas, il est intéressant de permettre à l’utilisateur de choisir lui même entre deux modes de fonctionnement. Par exemple, la gestion des « attachments » dans Gmail : par défaut, l’interface propose un certain nombre de fonctionnalités avancées (drag and drop, upload multiples), qui peuvent être désactivé d’un simple clic par l’utilisateur si celui-ci rencontre des disfonctionnements.

A l’inverse, on peut proposer un mode « amélioré » pour l’utilisateur, les « labs » (gmail, autre ?) sont des exemples parlants d’implémentation de feature flipping.

Pour cela, il suffit de proposer une interface accessible à l’utilisateur lui-même, lui permettant d’être maitre sur l’activation ou la désactivation de certaines fonctionnalités.

Gérer les fonctionnalités payantes

L’activation de fonctionnalités payantes selon différents plans, peut être complexe à mettre en œuvre et rajoute du code de type :

if current_user.current_plan == ‘enterprise’ || current_user.current_plan == ‘advanced’

Et que faire lorsque les exceptions arrivent :

  • l’entreprise « amie » paye le plan ‘basic’ mais on veut lui ouvrir toutes les fonctionnalités
  • la fonctionnalité était disponible sur le plan ‘advanced’ il y a 2 mois, mais aujourd’hui, le marketing veut qu’elle soit accessible uniquement aux plan ‘entreprise’… sauf pour ceux ayant souscrits il y a plus de 2 mois.

On peut utiliser le feature flipping pour éviter de gérer ces exceptions dans le code. Il suffit de conditionner l’activation de features à la souscription d’un abonnement. Lorsque l’utilisateur souscrit à un abonnement entreprise, on active les fonctionnalités X,Y et Z. On peut alors gérer les exceptions de façon simple dans l’interface d’administration.

« Graceful degradation »

Certaines fonctionnalités sont plus cruciales pour le business que d’autres. Lors de montées en charge importantes, il est judicieux de privilégier certaines fonctionnalités au détriment d’autres. Malheureusement, il est difficile de dire à son applicatif ou son serveur « traite en priorité tout ce qui concerne les paiements au détriment de l’affichage des graphiques de synthèse »… sauf si cette fonctionnalité d’affichage des graphiques est « feature flippée ».

Nous avons déjà parlé de l’importance de la mesure. Une fois cette mesure en place, il est alors trivial de flipper une fonctionnalité en fonction de celle-ci. Par exemple : « Si le temps de réponse moyen d’affichage des graphique dépasse 10 secondes, sur une période de 3 minutes, alors désactiver la fonctionnalité ».

Ceci permet de dégrader progressivement les fonctionnalités de site pour favoriser une expérience satisfaisante aux utilisateurs sur les fonctionnalités cœur de métier. Cela rejoint le pattern de « circuit breaker » (décrit dans le livre « Release It! » de Michel Nygard) permettant de court-circuiter le fonctionnement d’une fonctionnalité en cas d’indisponibilité d’un service externe.

Limites/contraintes

Comme évoqué précédemment, l’implémentation du feature flipping se fait simplement avec un ‘if’. Mais comme tout développement, cela peut facilement devenir une nouvelle source de complexité si on ne prend pas quelques précautions.

1 ‘if’ = 2 tests

Aujourd’hui, les tests automatisés restent la meilleure façon de vérifier le bon fonctionnement de son logiciel. Dans le cas d’un feature flipping, il faudra  au moins 2 tests : tester la fonctionnalité « flippé OFF » et tester la fonctionnalité « flippé ON ».

Lors d’un développement, on a souvent tendance à oublier de tester la fonctionnalité « OFF », pourtant c’est bien ce que verront tous vos utilisateurs avant que vous l’ayez activé. Donc, encore une fois, l’application du TDD est une bonne solution : les tests écrits lors du développement initial garantissent l’existence des tests sur des fonctionnalités OFF.

Faire le ménage !

L’usage intensif du feature flipping peut aboutir à une accumulation de « if » rendant le code de plus en plus difficile à maintenir. Or, pour certaines de ces fonctionnalités, l’usage de flipping n’avait d’intérêt que pour garantir le déploiement continu.

Pour toutes les fonctionnalités qui ne devront plus jamais  être désactivées (fonctionnalités non payantes/optionnelles, et qui ne feront pas l’objet de mode dégradé car critiques d’un point de vue fonctionnel), il est important de supprimer le « if » pour alléger le code et conserver un code maintenable.

Il faut donc prévoir de consacrer un peu de temps après la mise en production pour « faire le ménage ». Comme toutes les tâches de « code refactoring », celles-ci sont d’autant plus légères qu’elles sont régulières.

Modifications lourdes (ie. changement de schéma relationnel)

Certaines fonctionnalités entrainent des modifications lourde de code et de modèle de données. Prenons pour exemple une table Personne qui comprend un champ Adresse. Pour répondre à de nouveaux besoins, on décide de séparer les tables comme suivant :

Feature Flipping

Pour gérer ce genre de cas, voici une stratégie que l’on peut mettre en œuvre :

  1. Ajouter la table Adresse (la base contient donc la colonne Adresse ET la table Adresse). L’applicatif, inchangé, utilise les anciennes colonnes.
  2. Modifier l’applicatif existant pour qu’il utilise les nouvelles tables.
  3. Migrer les données existantes et supprimer les colonnes non utilisées.
  4. A ce point, l’applicatif n’a souvent que peu évolué du point de vue utilisateur, mais il utilise le nouveau modèle de données.
  5. On peut alors commencer à développement les nouvelles fonctionnalités reposant sur le nouveau modèle de données, en utilisant le « feature flipping ».

Ce schéma est relativement simple et implique du downtime lors des différentes livraisons (points 2 et 4).

D’autres techniques sont possibles pour gérer plusieurs versions de schéma en parallèle, rejoignant les concepts du pattern « zero-downtime deployment », permettant de mettre à jour le schéma relationnel sans impacter la disponibilité de l’application qui l’utilise, à l’aide de différents types de scripts (« expansion » et « contraction scripts »), de triggers pour synchroniser les données, ou encore de vues pour designer une couche d’abstraction exposée à l’applicatif.

Beaucoup moins fréquents que les modifications de code, les changements de schéma relationnel sont complexes et doivent être anticipés et gérés avec la plus grande attention.

Chez qui ça fonctionne ?

Cela fonctionne chez nous, bien que nous ne soyons pas (encore) des grands du web J :

Sur le projet Appaloosa, nous avons mis en place avec succès les différents patterns expliqués dans cet article.

Chez les grands du web, leur taille, les contraintes de déploiements sur plusieurs sites, les migrations de données très volumineuses, les forcent à mettre en place de tels mécanismes. Parmi les plus connus, on peut citer Facebook, Flickr ou Lyris inc. Plus près de chez nous on trouve Meetic ou la BNF.

Et chez moi ?

Comme on a pu le voir, l’implémentation est très simple et ce pattern peut être mis en place extrêmement rapidement. Il existe différentes implémentations dans différents langages comme la gem rollout en ruby ou feature-flipper en Grails, mais c’est tellement simple que vous voudrez surement faire votre propre implémentation pour coller au plus près de vos besoins.

Les avantages et les utilisations sont multiples, donc si vous avez besoin de déployer progressivement des fonctionnalités ou de tester sur des groupes d’utilisateurs ou de déployer en continue, il ne vous reste plus qu’à commencer.

Retrouver toutes les pratiques des Géants du Web sur le site dédié (www.geantsduweb.com) : pdf de l'ouvrage à télécharger, vidéo et compte-rendu de la présentation "Décrypter les secrets des Géants du Web"

Sources