Pourquoi et comment retarder au plus tard les choix techniques non-structurants

Introduction

La raison principale pour laquelle on crée un produit est d’apporter de la valeur au métier. C’est cet objectif qui uni les développeurs au métier : rechercher ensemble des solutions pour amener cette valeur le plus rapidement possible en production. Tant que le code n’est pas livré sur cet environnement, il n’est pas confronté à son utilisation et n’apporte rien aux utilisateurs. L'agilité a cette vocation d’amener au plus vite de la valeur, mais seule elle n’est pas suffisante.

En effet, il est possible et important de concevoir en amont le produit que nous voulons utiliser. Il faut néanmoins garder à l'esprit qu’il va souvent exister un décalage entre des idées sur papier et leur implémentation.

Pour y répondre, une bonne pratique est de garder le focus sur la modélisation et l’implémentation du cœur métier durant les premières itérations. En faisant cela, on a l’avantage de pouvoir rapidement avoir un feedback des utilisateurs sur leurs besoins, et d’éliminer les incertitudes au plus tôt en stabilisant le socle métier au plus vite.

On peut alors se dire : "concentrons-nous sur le développement de ce cœur métier au début du projet et mettons en place les solutions techniques dans un second temps”.

Les développeurs répondront : “C’est bien beau, mais de notre côté il y a des incertitudes techniques, et nous devons les dérisquer au plus vite”.

Effectivement lorsqu'on développe un produit on cherche à répondre à ces deux questions :

  • Quels sont les problèmes métiers ?
  • Quelles solutions techniques mettre en place pour y répondre ?

L’important est d’arriver à répondre au deux en parallèle, car aucune ne doit être négligée au risque d’avoir un produit instable et/ou qui réponde au mauvais besoin. L'agilité nous a appris comment être efficace sur la conception d’un produit afin d’en tirer le maximum de valeur. Pourquoi ne pas utiliser le même esprit en ce qui concerne la mise en place de solutions techniques ?

Il y a quelques années les socles techniques évoluaient peu dans le temps : c'était par exemple le cas avec les mainframes ou les serveurs d’application. On était contraint d’anticiper la technique au détriment du métier. Aujourd’hui, des technologies peuvent nous aider à gagner en souplesse. Il devient alors possible de passer du temps à implémenter le métier pour que toutes les parties prenantes du projet en aient une conception commune. Dans un second temps, on peut s’attaquer à la question des technologies, en utilisant des solutions comme Docker ou le cloud.

Avec quelques pratiques de développement, on peut se faciliter la vie pour repousser de quelques semaines le choix des technologies utilisées, que l’on considère généralement comme structurant. Nous verrons 3 de ces techniques pour vous aider dans vos développements :

  • Isoler les dépendances par un modèle In-Memory
  • Utiliser des spikes
  • Livrer de l’Infrastructure as Code (IaC)

Isoler les dépendances par un modèle In-Memory

Pourquoi ?

Au début d’un projet, on peut chercher à éviter de prendre des décisions du type “quelle base de données” ou “quel serveur d’authentification” utiliser lorsque l’on démarre une application. Sachant que mettre en place une base de données est quelque chose de maîtrisé, pourquoi décider dès le début ? On peut gagner à passer les premières itérations à éclairer les incertitudes métier. Une fois que cela sera fait, les discussions entre développeurs pour décider de la technologie à utiliser pourront se baser sur le besoin avéré et ainsi éviter de la sur-conception ou des choix erronés qui créeront une dette coûteuse. Pour cela, il est possible de s’abstraire des dépendances externes au départ et de plutôt utiliser des modèles In-Memory couplés à une isolation des dépendances. Pour la partie isolation de dépendances, le modèle d’architecture hexagonale est parfaitement adapté (voir cet article si vous voulez en savoir plus).

Comment ça marche ?

En architecture hexagonale, on va chercher à isoler le métier en le faisant interagir avec des dépendances extérieures via des interfaces. Ensuite lorsque l’on va mettre en place nos dépendances, celles-ci vont implémenter les interfaces. Ainsi le domaine métier reste toujours isolé et maître de son comportement.

Une technique simple au départ d’un projet sera d’implémenter ces interfaces avec des modèles In-Memory : des listes, maps, etc. Elles pourront très simplement répondre au besoin métier, nous permettant de livrer quelque chose de testable par les utilisateurs. Il faudra sans doute les remplacer à terme par une base persistante. À ce moment-là on aura déjà validé le cœur de l’application.

Exemple

Sur une application qui gère des utilisateurs, on commence par définir notre architecture hexagonale avec un usecase qui va dépendre de l’interface UserRepository du domaine métier :

On observe que les imports viennent uniquement du domaine : on sépare le métier des implémentations techniques.

Jetons alors un oeil à l’interface UserRepository :

Une fois que notre use-case et notre métier sont définis, on va pouvoir faire une première implémentation du Repository en utilisant une solution In Memory. Celle-ci est basique mais va nous permettre de valider le métier avant de nous poser la question du type de base de données à connecter.

Lorsque nous voudrons injecter les dépendances, nous pourrons utiliser cette implémentation dans un premier temps.

Pour voir le détail : https://github.com/jozolr/retarder-les-choix-structurants

Utiliser des spikes

Définition

Un spike s’apparente à un POC dans le sens où il permet de prouver ou infirmer des hypothèses. Là où il va se différencier est le contexte dans lequel on va le mettre en place : un spike s’intègre dans la vie d’un projet. Quand un POC permet d’évaluer la faisabilité d’un projet/produit, un spike va tenter de répondre à un problème unique en se débarrassant de toute autre hypothèse dans le cadre d’un projet/produit

Pourquoi ?

Grâce à des modèles In-Memory couplés à de l’architecture hexagonale, on se met en capacité d’intégrer plus tard des services dans un projet. Une des difficultés qui va apparaître est d’être capable de tester ces services pour choisir le plus adapté. Comme de nouvelles technologies, librairies ou architectures vont régulièrement sortir il est difficile voire impossible de les anticiper. Si on ajoute une tâche technique au backlog, celle-ci sera difficile à estimer car elle comportera beaucoup d’inconnues. Pour se donner les moyens de tester et de s’améliorer en permanence tout en limitant les inconnues nous pouvons utiliser des spikes. Cette technique permet de se découpler des problématiques d’un projet et de nous aider à tester et mettre en place ce qui est nécessaire quand c’est nécessaire.

Comment ça marche ?

L’idée est de s’isoler du projet sur lequel on est pour ne pas être impacté par des éléments extérieurs à ce qu’on veut tester. On peut y parvenir en créant un nouveau projet vierge et en implémentant directement la nouvelle technologie ou le nouveau concept. Les points d’attention sont :

  • Fixer un objectif mesurable avant de se lancer : quel est le problème auquel on cherche à répondre
  • Délimiter le problème à résoudre à sa plus simple expression
  • Fixer un temps pour l’expérimentation : entre une demi-journée et un jour. Au delà le problème est trop gros, ou vous partez trop loin
  • A l’issue de l’expérimentation, décrire un équivalent de GO/NO GO et éventuellement des tâches à intégrer au backlog

Il est possible et conseillé d’intégrer des tâches de type spike dans le backlog d’un projet. Ils représentent en quelque sorte une R&D qui permet à l’équipe de monter en compétences et de s’investir. Ce format a un autre avantage : c’est un travail de capitalisation dont les résultats sont utilisables au-delà d’un seul projet.

Exemple

Pour l’exemple précédent sur l’isolation de dépendance, nous avons bootstrappé une application dont l’unique objectif est de faire un focus sur l’isolation des dépendances. Ce spike est maintenant présent dans un repository, a démontré la faisabilité d’une telle technique, et pourra être partagé.

Un autre exemple que nous avons pu appliquer sur un de nos projets : la mise en place d’un outil de migration de base de données. Au début du projet nous n’avions pas de besoin de l’avoir pour valider les premières features métier, alors nous avons repoussé sa mise en place. Lorsque les premières douleurs sont ressenties, nous avons regardé ce qui se faisait. Avant de se lancer sur une tâche technique, nous avons effectué un spike pour tester une solution, Flyway, en dehors du contexte métier du projet. En une journée nous avions validé son fonctionnement et avions une idée claire de la manière de l'intégrer au projet.

Dans le cas où le spike est un NO GO, il faut le voir comme du temps gagné sur le projet : on aura invalidé rapidement une hypothèse sans ajouter de dette à l’application. C’est l’idée du fail fast : on limite l’impact des erreurs en les ayant au plus tôt.

Source: http://www.extremeprogramming.org/rules/spike.html

Livrer de l’infrastructure as code

Pourquoi ?

En utilisant des spikes on prend l’habitude de tester et d’intégrer des nouveaux concepts régulièrement dans nos applications. Cantonné au code applicatif on sera cependant limité : si j’ai besoin d’utiliser au bout de quelques mois une base de données je devrais me coordonner avec les équipes ops pour le déploiement. Quand cela concerne des environnements de développement, c’est une contrainte qui fait perdre en souplesse.

Dans un schéma “classique” où les développeurs vont livrer des artefacts applicatifs, et les ops vont les deployer, on se retrouve avec une synchronisation complexe entre les deux parties. Cela peut amener à essayer de mettre au plus vite en place une stack d’infrastructure qui aura le défaut d’apporter une inertie importante. Afin de maintenir des échanges efficaces entre les équipes, et de garder de la flexibilité sur la mise en place de nouvelles couches d’infrastructure, on peut utiliser de l’infrastructure as code. Ce code est présent dans un repository partagé par les développeurs et les ops.

Quand cela est possible, on obtient la même flexibilité à livrer des features applicatives que des services au sein de l’écosystème. Cela laisse donc la possibilité de retarder la mise en place de services annexes (base de données, file de messages, etc) lorsque l’on en aura réellement besoin, et lorsque l’on sera capable de juger de la meilleure solution technique.

Comment ça marche ?

Pour livrer et déployer des artefacts, on peut utiliser des pipelines de déploiement. Celles-ci vont décrire les étapes successives pour construire et livrer les artefacts au sein de l’infrastructure. Elles peuvent notamment être décrites en tant que code, et donc être à la main des ops et des développeurs.

Que l’infrastructure soit composée de machines virtuelles dans lesquelles on va exécuter un script Ansible, ou d’un cluster Kubernetes où l’on va appliquer des scripts de configuration, les contextes sont nombreux pour nous permettre d’avoir ce “terrain partagé”.

Un développeur peut alors décrire le déploiement d’une base de données, d’un nouveau service ou d’une nouvelle application au sein de l'écosystème de manière autonome. Il pourra indiquer sa configuration dans le script de déploiement et sa fonctionnalité sera livrée intégralement.

Cette technique est plus complexe à mettre en place que les précédentes : elle nécessite d’avoir une organisation souple entre la partie applicative et infrastructure. Cependant à partir du moment où elle est en place l’autonomie des équipes permet un gain en agilité qui va amener un rythme de livraison rapide. Cette autonomie est primordiale pour optimiser au mieux les développements. Celle-ci est détaillée dans l’ouvrage Accelerate: The Science Behind Devops qui démontre l’importance de casser le mur entre développeurs et ops (si vous voulez en savoir plus, cet article le résume).

Conclusion

Développer un produit nous amène à répondre à plusieurs questions :

  • que livrer ?
  • comment le livrer ?
  • quand le livrer ?

Aujourd’hui, le constat est que ce qui a vocation à subsister dans le temps, ce n’est pas les technologies mais le cœur du métier. Alors pour nous adapter, nous avons vu 3 techniques qui nous servent à retarder au plus tard les choix techniques non-structurants, et à valoriser ce qui doit être le plus important, à savoir le métier.

Isoler les dépendances par un modèle In-Memory

Comment s’assurer que le focus d’un début de projet se fera sur le cœur des fonctionnalités métier ? Par l’utilisation d’architecture hexagonale et du modèle In-Memory. Cette technique est la plus activable, vous pouvez dès demain l’utiliser sur votre projet !

Utiliser des spikes

Comment se donner la possibilité de tester et innover le long d’un projet sans le mettre en risque ? Via l’utilisation de Spikes. Cela demande une structuration de vos process pour laisser la liberté aux équipes d’explorer avec des objectifs précis et atteignables.

Infrastructure as code

Le mur entre le métier et les développeurs a été cassé avec l’agilité, l’étape suivante est de briser celui avec les ops avec de l’infrastructure as code. Cela amènera une capacité de livrer rapidement et d’avoir des feedbacks plus rapides, et ainsi de maintenir la capacité à innover.