Archive for janvier, 2019

Les Feature Toggles* : Activer / Désactiver dynamiquement une fonctionnalité d’un logiciel

* La désignation ne fait pas consensus et la liste des termes synonymes est très longue : Feature Flag, Feature Switch, Conditional Feature, Feature flipper, Feature Bits, Gatekeepers….

Le principe

À la livraison d’une nouvelle version d’un logiciel sur un environnement, toutes les nouvelles fonctionnalités sont rendues disponibles immédiatement. Au contraire, le modèle de design Feature Toggle* a pour but de fournir un interrupteur de fonctionnalités qui active ou désactive une partie du code sans nécessiter ni redéploiement ni redémarrage de l’application.

Les usages possibles

Cette fonctionnalité peut avoir de nombreux usages. Les cas suivants en sont un exemple et ne représentent pas l’exhaustivité des situations.

CAS 1 – Différents besoins sur différents environnements

Pratiquer des livraisons régulières, fréquentes et de petite taille est une des conditions à garantir pour l’agilité d’un projet (Continuous Delivery). L’enjeu est le maintien d’un cycle de développement court pour limiter les risques du rythme élevé de changements. Or si cette stratégie s’avère un atout en environnement de recette, où la validation de petites briques logicielles réduit la durée de la phase de tests, elle peut être difficile à réaliser en environnement de production. En effet, une fonctionnalité peut représenter plusieurs itérations de développements et il serait incongru de la mettre à disposition de l’utilisateur de façon incomplète. Le système de feature toggles rend possible la livraison de parties de fonctionnalités en mode désactivé sans que cela n’affecte la stabilité du code. L’activation pourra être réalisée au bout de quelques semaines, une fois que l’ensemble du code nécessaire aura été livré.

CAS 2 – Incident suite à une livraison

Lorsqu’un dysfonctionnement est rencontré en production par un utilisateur suite à une récente livraison, il sera possible de désactiver la fonctionnalité pour réduire l’instabilité du logiciel en attendant une résolution.

CAS 3 – Dépendances entre équipes

Il n’est pas rare que plusieurs équipes travaillent en parallèle sur des besoins différents et dans des délais qui leur sont propres tout en ayant en commun une ou plusieurs applications. Par exemple, l’interface peut être la même pour toutes les équipes. Lorsque le moment est venu pour une équipe de livrer son travail, elle se trouve gênée par les développements encore en cours ou en validation des autres équipes sur l’application partagée. Si chaque équipe a mis en place des toggles,  la livraison sera envisageable à la condition que chaque fonctionnalité non encore terminée soit désactivée.

CAS 4 – Tests comparatifs

Il existe une catégorie de Feature toggles dont l’activation se fait par utilisateur. La condition d’activation peut être déterminée par un profil de permissions ou encore l’appartenance à un échantillon. Au lieu de plusieurs environnements, un seul est nécessaire pour comparer des résultats entre différents groupe d’utilisateurs et valider une approche.

AUTRES CAS – L’activation de fonctionnalités peut aussi…

  • concerner la configuration d’un système opérationnel déjà en service afin de déterminer les meilleurs paramètres (court terme) ou bien de maintenir un service stratégique au détriment d’un autre en dégradant les performances à son profit (long terme)
  • être amenée à rester sur le long terme dans le cas du besoin de customisation de fonctionnalités par utilisateur (par exemple un mode « utilisateur premium»)

Selon Martin Fowler 1 – son blog, il existe deux axes de qualification pour un feature toggle, son dynamisme (changements au déploiement / à chaud / par requête) et sa longévité (nombre de jours / mois / années). Cette classification doit orienter vers le choix d’une implémentation.

Un rapide aperçu avec FF4J

FF4J  (Feature Flipping for Java) est un framework en licence libre apache 2.

Une fois les dépendances nécessaires ajoutées (ff4j-core, junit, ff4j-web) et la configuration paramétrée, il faudra créer un service fournissant une instance de la classe FF4J.

Étape 1. Créer un feature toggle

Cela consiste à insérer une nouvelle entrée dans un feature store (le système de stockage) précisant l’identifiant du toggle, sa description, son statut d’activation, son groupe d’appartenance, les rôles d’utilisateurs ou les stratégies associés. L’offre de système de stockage des données est large : mémoire, bases relationnelles ou no-sql.

CREATE TABLE FF4J_FEATURES (
  "FEAT_UID"            VARCHAR(100),
  "ENABLE"              INTEGER NOT NULL,
  "DESCRIPTION"         VARCHAR(1000),
  "STRATEGY"            VARCHAR(1000),
  "EXPRESSION"      VARCHAR(255),
  "GROUPNAME"           VARCHAR(100),
  PRIMARY KEY("FEAT_UID")
);

ci-dessus : Table prévue pour le stockage en base relationnelle, le champ « enable » stocke 1 pour le statut « activé » 0 pour « désactivé ».

Étape 2. Utiliser le feature toggle dans le code

L’identifiant choisi à l’étape 1 permettra de vérifier l’état d’activation.

If (ff4j.check("id.du.toggle")) {
	doBehiavorA()
}  else {
	doBehiavorB()
}

ci-dessus : si le toggle est activé alors le comportement A est joué sinon le comportement B fonctionne.

Étape 3 . Déployer le code sur un environnement

Étape 4 . Contrôler l’activation via la console d’administration

ci -dessus : Une interface graphique permet de réaliser l’opération sans nécessiter de compétences techniques.

Limites

     Ce mécanisme complexifie le code : deux cas de comportements sont implémentés en même temps donc deux fois plus de code. Il est donc conseillé de réfléchir aux bons emplacements stratégiques dans le code et de limiter le nombre de recours. Dans le même ordre d’idée, le nombre total de fonctionnalités couvertes se doit de rester faible pour rester maintenable.
     Heureusement, ce risque est diminué dans la plupart des usages puisqu’ils répondent à un besoin temporaire. Néanmoins, cette situation temporaire va induire une opération supplémentaire de nettoyage du code, une fois que la fonctionnalité est activée de manière définitive.
     Il existe également quelques cas où la sécurisation avec un toggle n’est pas envisageable. J’ai notamment déjà rencontré le cas lors d’un changement structurel important au niveau du modèle en projet .
     Il existe une controverse. En effet, les outils de gestion de versions (tel que Git) apportent une aide précieuse dans la résolution de certaines problématiques (ex. CAS 3). Si la pratique très courante du Feature Branching (séparation des fonctionnalités en branches) est jugée inadaptée face au défi de l’intégration et de la livraison continues pour les uns 1 – martin fowler blog, elle reste une réponse possible lorsque bien exécutée pour d’autres 2 – james mckay blog. Ces approches peuvent aussi être utilisées côte à côte.

     Enfin, cette approche va de pair et prendra tout son sens dans un contexte où l’automatisation des tests et des déploiements est déjà en place.

Quelques implémentations

Flaggr (Android), angular-feature-flags (Angular), rollout.io (javascript), Togglz (Java), au sein du framework Spring (Java)…

 

Les approches Test Driven Design et Behaviour Driven Design

Les erreurs de code ont un coût pour le commanditaire d’un logiciel. Lorsqu’elles surviennent en phase de production, elles ralentissent son activité. Durant le processus de recette, un nombre élevé de bogues dans une version retardera sa livraison. Il est donc important de minimiser les risques de survenue de ces comportements inattendus et des régressions de fonctionnalités dès la phase de développement.

Test Driven Design

Le Test Driven Design (TDD) place l’écriture des tests unitaires au rang de bonne pratique fondamentale dans la lutte contre ces risques. Le TDD préconise de commencer par l’écriture de tests, puis de procéder par itérations successives, alternant le lancement des tests avec l’implémentation ou la refonte du code livrable. La règle à suivre lors de la phase d’implémentation est de ne toujours implémenter que le minimum faisant passer le test. Une phase de remaniement (refactoring) peut s’avérer nécessaire une fois que l’ensemble des tests passe au vert pour maximiser sa qualité.

l’approche TDD

Les bénéfices

De cette manière, l’accent est mis sur le contrat que doit remplir la méthode testée. La réussite de tous les tests garantit que le contrat est rempli. Le développeur peut en disposer comme d’un outil de vérification pour faire évoluer le code sans risque de régression.
La qualité du code est également améliorée. D’une part, le code orienté par les tests se révèle souvent plus lisible car il tend à ne comporter que le minimum nécessaire.
D’autre part, les tests définissent une documentation précieuse d’une méthode en terme d’entrée et de sortie et de comportements attendus. C’est un atout intéressant dans le contexte de projet agile, aux mises à jour de code fréquentes, et de projet avec un renouvellement fréquent de l’équipe, face au besoin de montée en compétence rapide.
Cependant, le TDD ne résout pas tous les problèmes, en partie, car les tests sont orientés techniques.

Les limites

Dans la plupart des projets, le besoin du métier est recueilli par les profils fonctionnels. Or, avec l’approche TDD, le développeur est le seul profil de l’équipe qui écrit et déroule les tests. La réussite de ceux-ci dépendra de leur qualité, et en particulier, d’une bonne compréhension du besoin du métier, traduit dans la bonne granularité et avec une bonne couverture de tests.
Malgré la préconisation d’une implémentation minimale à chaque itération, il existe encore un risque que le développeur parvienne à faire passer les tests dans le non-respect des bonnes pratiques de programmation (nommages, découpage du code…) qui rendrait le code moins évolutif. Néanmoins, la présence des tests permettra de ré-usiner le code sans risque par la suite.
Enfin,en s’appuyant uniquement sur les tests unitaires, par définition indépendants et reproductibles unitairement, la vérification de la bonne marche d’un comportement du logiciel qui serait la succession de comportements interdépendants dans un contexte précis n’est pas envisagée.
L’approche BDD a été pensée pour répondre à la majorité de ces limites.

Behaviour Driven Design

Le Behaviour Driven Design (BDD) étend le potentiel du TDD en proposant la définition de tests orientés comportement, écrits par un profil fonctionnel dans un langage naturel. Les cas de tests sont structurés selon le modèle GIVEN-WHEN-THEN (nommé Gherkin). Comme le montre l’exemple ci-après, GIVEN décrit le contexte, WHEN l’action de l’utilisateur et THEN les comportements attendus.

SCENARIO paiement avec un code promotionnel
GIVEN  le client  « Mme D. » est authentifié
AND son panier comporte les produits suivants
     produit  | prix | quantité
     robe     |  65  |        1
AND le client a une adresse de livraison
AND le client a choisi un mode de paiement
AND le client indique le code promotionnel «MOINS10%»
WHEN le client  demande le paiement de sa commande
THEN le panier est vide
AND la facture donnée indique
    nom    | produit | prix total
    Mme D. |  1 robe |       58,5


La tenue d’un tri-amigos, une réunion confrontant les visions des profils fonctionnels et des profils techniques de l’équipe, favorise l’explicitation de tous les scénarios significatifs, y compris les traitement des erreurs, et la dissipation des imprécisions
Le fichier feature produit sert de trame pour le développeur. Chaque phrase est traduite sous forme d’une méthode, réalisant soit l’initialisation des conditions (GIVEN), soit les opérations (WHEN), soit les assertions de comportements (THEN) définis par le scénario. Le test est déroulé dans l’ordre des phrases par appels successifs de l’ensemble de ces méthodes interdépendantes.


L’approche BDD complète l’approche TDD en incluant les membres fonctionnels de l’équipe

Les bénéfices

La description des attentes est disponible pour tous dans un format compréhensible par tous. En favorisant la discussion entre fonctionnels et techniques, cette approche peut mener à l’émergence d’un langage commun aussi bien dans les définitions de tests que dans le code. Ce sera un atout pour la communication au sein de l’équipe.
Enfin, la mise en place du BDD contraint le développement à la validation de scénarios (tests d’intégration) là où le TDD ne contraint qu’à la validation de règles métier sans liaison entre elles (tests unitaires).

Les limites

La mise en place initiale de cette approche peut être longue avant que l’équipe ne soit suffisamment experte, mais sur le long terme, la présence des BDD deviendra un gain de temps en diminuant le nombre d’incidents.

Conclusion

Ces approches apportent au produit une meilleure stabilité et une meilleure documentation, et à l’équipe une vision partagée des problématiques métier. Afin de s’assurer de profiter de leurs bénéfices, les tests BDD et TDD doivent être lancés régulièrement avant chaque livraison et de préférence dès la livraison sur un environnement d’intégration. Il est fortement recommandé d’automatiser leur lancement.
Néanmoins, elles ne sont pas suffisamment contraignantes pour lutter contre d’autres facteurs d’apparition de bogues comme un mauvais design. De bonnes pratiques de développement doivent être mises en place comme par exemple le pair programming (code en binôme) ou la revue de code.