Cet article fait suite à celui où je cause de l’émergence de Big Data dans le cadre d’une évolution architecturale du SI. Je prenais l’exemple de la transition de l’architecture du SI d’Amazon d’un modèle client-serveur web vers un modèle de services. Je vais ici pousser le concept plus loin en vous présentant un autre buzzword, le microservice que je vais extraire de sa gangue de hype.
2015 a été l’année du microservice. Cette soudaine hype a certainement été due au fait que Netflix a largement communiqué sur le design de son architecture [1] et que les influenceurs du domaine, Fowler en tête avaient mûri et formalisé le concept à peu près au même moment [2].
Si vous lisez mes articles, vous devez savoir quel prix j’attache à la hype dès qu’il s’agit de technologie et d’architecture. Zéro. Et ce dans n’importe quelle devise. Cela n’empêche que tout le foin qu’on fait autour d’un concept ne rend pas ce concept inintéressant, au contraire. Le but n’est pas de comprendre comment la hype se forme et s’étend, certains s’y sont déjà intéressés. Non, l’objectif est bien celui annoncé en introduction, débarrasser un concept de toute pensée magique et de toutes fausses promesses que des prophètes de seconde main souvent largement incompétents lui attachent pour être parfaitement informé et prendre les meilleures décisions possibles.
Nous allons donc présenter le concept de microservice d’un point de vue de l’architecture du SIE en omettant à dessein toute référence technologique et toutes les belles histoires qui présentent ce style d’architecture comme la panacée. Il n’en est rien. Dès qu’on parle d’architecture logicielle que ce soit à l’échelle d’une application ou d’un SIE, il s’agit de faire des compromis d’ingénierie et d’être réaliste.
Naissance d’un concept
Les raisons qui ont motivé l’apparition des architectures à base de microservices sont de deux ordres. Au premier ordre, on peut dire que microservices et SOA même combat dès lors qu’il s’agit de découpler les fonctions métier du SIE. Au second ordre, la mise en place et la pratique de SOA étant devenues fort complexes, une volonté est apparue de rendre les choses plus directes et moins normatives.
Après quelques années de pratique de la SOA, le constat peut être fait que les services conçus et maintenus par les DSI n’ont rien à envier aux silos qu’ils étaient censés remplacer : projets longs et incertains, équipes pléthoriques, accumulation de dette technique, j’en passe et des meilleures. S’il faut distinguer une explication et une seule à cette situation, ce sera celle de l’interdépendance entre services. Au fur et à mesure que le temps passe, le besoin de nouvelles fonctionnalités, leur complexité intrinsèque et leurs interactions ont fait qu’il devenait impossible d’avoir une vue globale non seulement de l’architecture mais aussi des différents projets qui concouraient à son évolution.
C’est là qu’entre en jeu la stratégie « Bezos » qui n’est qu’une énième variante du « diviser pour mieux régner ». Puisque les services sont trop gros (comprendre possèdent trop de fonctionnalités, trop de couches logicielles et trop de bitoniaux techniques différents), faisons les maigrir ! Et puisque les projets sont aussi trop gros, faisons les aussi maigrir !
Plus sérieusement, l’hypothèse qui est faite en l’état est la suivante : en vertu du principe de Conway, plusieurs équipes interdisciplinaires à faible effectif produiront une architecture similaire à leur structure. Amazon (encore eux !) ont également adopté le principe du « You build it ? You run it ! » [3]. En d’autres termes, il s’agit aussi pour l’équipe de développement d’assurer le déploiement et l’exploitation du composant. Pour comprendre les bénéfices que l’on peut en tirer et pour dévoiler que ce principe est celui qui sous-entend une autre hype, celle de 2012 qui concernait DevOps, je vous suggère de lire deux articles disponibles sur ce blog : DevOps, pourquoi n’y avons-nous pas pensé plus tôt ? et DevOps, comment allons-nous nous y prendre ?.
Le concept de microservice émerge des conditions dans lesquelles on fait baigner les équipes de développement. Une politique claire de composition et de taille d’équipe ne fait pas tout car le principe de Conway n’est qu’un principe empirique pas une loi. Il existe deux autres conditions pour s’assurer qu’une architecture de microservices est viable. La première condition est que chaque service ne partage jamais son contexte d’exécution avec un autre service. La seconde condition est de disposer d’une solution de communication interservices.
Le microservice sous le microscope
Un microservice est donc un service (au sens de la SOA) qui ne partage jamais son contexte d’exécution avec d’autres microservices mais qui peut communiquer avec eux. Mais le style d’architecture basé sur les microservices n’est pas SOA.
Au fil des années, les architectures basées sur les principes de la SOA sont devenues aussi complexes que les architectures monolithiques qu’elles étaient censées remplacer et dépasser. De la chorégraphie de services à la composition des processus (à moins que ce ne soit le contraire, on s’y perd) en passant par l’usage immodéré de solutions de communications complexes et fragiles (Oui, ESB, c’est de toi dont je parle !), le SIE est devenu un zoo technologique bien compartimenté où la seule chose qui fonctionne bien c’est la sécurité de l’emploi des experts middleware et des architectes J2EE.
Les microservices prennent le contrepied de cette situation et partent du principe qu’un service doit faire une seule chose et la faire bien. Ce principe n’est pas nouveau puisqu’il s’agit de celui qui sous-tend le développement sous UNIX depuis les origines : « Do One Thing And Do It Well » et « Keep It Simple, Stupid! ».
Plutôt que d’énoncer tout de suite la douzaine de règles qui gouvernent cette approche de la conception de logiciels, examinons plutôt les conséquences. Ce qui surprendra la plupart des personnes à qui vous expliquerez les principes de base d’une architecture de microservices c’est l’absence de base de données partagée. Cela est du à des années d’omniprésence des SGBDR au sein des DSI et de leur lente transformation en silos faisant l’objet d’un culte par des équipes de DBA. Un microservice, s’il a besoin de rendre un état persistant ou de stocker des données externes, s’appuiera sur un système de persistance qu’il sera le seul à administrer et à utiliser.
Cela permet d’éviter plusieurs choses. Tout d’abord cela fait disparaître le couplage entre services via les schémas de base de données. La seule qui doit contenir de la sémantique est l’API des services. Pas le schéma d’une table. Cela évite également le couplage entre les équipes. En revanche, partitionner l’espace des données d’entreprise de cette façon impose de se confronter à des mises à jour de données pour éviter les incohérences. Une solution est de mettre en œuvre un programme de MDM (Master Data Management). Je ne pense pas que ce soit une bonne idée car cela introduit un couplage indirect entre les back-ends de persistance des différents microservices et va induire le besoin d’avoir une équipe transverse et mono-disciplinaire qui travaillera sur un projet dont le contexte d’exécution sera par nature partagé entre toutes les bases de données. Cela va un peu à l’encontre des principes ontologiques de l’architecture. Ce que je préconise est, de fait, d’éviter d’avoir à faire ce travail de nettoyage et de vérification des données en faisant en sorte que les erreurs de mises à jour ne surviennent jamais. Comment ? En ne faisant jamais ni mise à jour ni suppression et en gardant l’historique des changements de chaque entité. Utopique ? Non, c’est l’objectif avéré des propositions comme les architectures lambda/kappa [4], [5] ou la ségrégation commande/requête [6]. C’est d’autant plus envisageable que la technologie nous le rend faisable grâce à un serveur de base de données possédant un système de gestion de versions concurrentes (MVCC) comme MongoDB ou PostgreSQL. C’est à l’équipe en charge du cycle de vie du microservice que revient la décision de sélectionner les technologies d’implémentation de ce microservice et non plus au directeur technique de la division ou à un consultant ou à un autre intervenant n’étant pas partie prenante dans le cycle de vie du service.
Une autre conséquence de l’architecture en microservices est que l’équipe est responsable de son service sur l’ensemble de son cycle de vie, de l’expression des besoins à la mise hors-ligne du service. Cela veut dire que l’équipe spécifie, conçoit, implémente, teste, intègre, déploie le service mais plus encore, assure le support utilisateur et la maintenance. Vous comprenez pourquoi l’interdisciplinarité est importante et pas seulement du fait d’un mix technologique particulier. L’avantage, d’après Werner Vogels est qu’avoir une équipe qui s’est frottée aux utilisateurs (qui sont souvent les membres d’une équipe responsable d’un autre microservice) améliore la qualité opérationnelle du microservice. Je vois dans cette approche le piège, facilement contournable, de voir certains membres de l’équipe se lasser d’en faire partie, je parle surtout des développeurs qui comme tous les créatifs ont besoin de changer d’air régulièrement. L’architecture en microservices impose, de ce fait, une responsabilité importante aux managers (mais ne l’avaient-ils pas déjà ?)
Maintenant que nous avons vu les deux principes très structurants sur l’organisation de la DSI de l’adoption des microservices, regardons en détail leurs conséquences sur le design de l’architecture et les opportunités que cela ouvre.
La première propriété émergente d’une architecture de microservices est l’isolation. Cette propriété caractérise justement les systèmes faiblement couplés et offre plusieurs avantages. Tout d’abord cela rend le système plus résilient dans le sens où cela minimise les conséquences d’une panne locale. Le système est également rendu plus robuste dans le sens où la probabilité d’occurrence d’une panne globale est minime. Comment est-ce possible ? D’une part parce que les services sont conçus de telle manière à ne pas dépendre les uns des autres de manière continuelle et d’autre part parce qu’il est improbable que tous les services tombent en panne en même temps. Cela contraste avec les composants d’un système monolithique qui sont fortement couplées et continuellement interdépendants.
Bien entendu, cela n’arrive pas « magiquement » en arguant simplement du fait qu’on fait de l’architecture de microservice !
Un composant n’est pas un microservice simplement parce qu’on désire qu’il en soit un. Il est nécessaire de le concevoir selon certaines règles. Nous avons déjà vu que le back-end de persistance devait être exclusivement réservé au microservice qui en a l’usage. Une autre contrainte à respecter autant que faire se peut est que le service soit à état minimal (et, idéalement, sans état) en ce qui concerne ses communications. Cette propriété signifie que les communications prises en charge par le service (les requêtes, transactions, etc.) ne doivent pas comporter d’information de session. En d’autres termes, le client d’un service doit fournir au service en une seule fois toutes les informations nécessaires au traitement de sa requête. Ce comportement est d’ailleurs bien connu des praticiens de RESTful HTTP mais contrairement à ce dernier protocole, les protocoles pour microservices appliquent également cette propriété au client du microservice (qui est, rappelons-le aussi un microservice). Cela revient à dire que l’état transmis par un client à un serveur doit lui être retransmis dans la réponse.
Cette propriété est réalisable si l’on possède un système de communication unifié pour tous les microservices basé sur un protocole universel. RESTful HTTP est une possibilité à considérer si les microservices possèdent une API modélisable en termes de ressources sur lesquelles appliquer un modèle d’action CRUD (Create/Read/Update/Delete). Ce n’est pas toujours le cas car un tel protocole est basé sur des liaisons point-à-point ce qui pose un problème de combinatoire dès que les microservices vont se parler les uns aux autres. Une bonne idée est de combiner deux protocoles. Si l’on modélise les relations de couplage faible entre services déduites de leurs communications, on aboutit à un graphe orienté acyclique dans lequel on peut identifier des sources (les services que personne n’appelle mais qui en appellent d’autres), des puits (les services qui n’appellent personne mais qui sont appelés par d’autres) et des nœuds intermédiaires (qui appellent et sont appelés). Sources et puits peuvent respectivement être des clients et serveurs RESTful HTTP (ou n’importe quel protocole sans état basé sur TCP). Pour les autres services, un protocole basé sur le pattern Publish/Subscribe avec ou sans broker permet de maîtriser la combinatoire des liens de communication antre services. Des solutions technologiques existent allant de RabbitMQ à Kafka en passant par ZeroMQ.
Après la complexité induite par la non-globalité des données persistantes, par la combinatoire des communications, il existe une dernière source de complexité due à l’hétérogénéité des cibles du déploiement des microservices. Pour maîtriser cette complexité il est nécessaire d’avoir recours à des conteneurs de services. Et là, pas de mystère, on utilise Docker sur une machine virtuelle Linux. Je dis ça mais vous pouvez aussi utiliser un serveur sous Windows si ça vous chante, Docker est supporté par Microsoft maintenant.
Vers l’infini et au-delà
Comment mettre en place une architecture de microservices ou réaliser la transition vers ce type d’architecture ? Tout d’abord, posez-vous la question si le jeu en vaut la chandelle. Une telle architecture est une des réponses possibles lorsque l’innovation dans le métier est fréquente et incertaine et que le SIE doit la suivre. Un SIE architecturé sur les principes des microservices est dynamique et scalable même si sa gouvernance, devenue décentralisée, n’a plus rien à voir avec ce qui prévalait lorsque le SIE était composé de blocs fonctionnels plus lourds. Pensez-donc à la conduite du changement. Et posez-vous la question de la possibilité d’externaliser le développement de microservices auprès d’un prestataire. Je rappelle que le développement d’un microservice par une équipe autonome et indépendante n’est pas une option, c’est une condition nécessaire pour que cette approche soit viable. Vérifiez que votre prestataire le comprenne bien et qu’il possède la capacité de réaliser ce développement. Si l’équipe off-shore en question adhère aux principes de l’architecture de microservices en ce qui concerne la continuité de déploiement et de l’exploitation, il y a fort à parier qu’une telle relation suivie organisée autour de la maintenance d’un microservice ravira son employeur qui aura ainsi une source de revenu stable et prévisible sur le moyen terme. Le pré-requis de cela est que votre organisation (et vous-même) se soit déjà convertie à ces principes et les ait déjà mis en application.
Un dernier conseil, l’organisation d’une équipe en charge d’un service s’accommode mal des prétendues méthodes « agile » qui ne s’emploient efficacement que pour des projets courts et des produits logiciels jetables. Les approches d’organisation inspirées de « Lean Software Management » sont moins nocives mais tout aussi inutiles. Lorsqu’on pratique l’architecture de microservices, on cherche une forme de stabilité dans le changement permanent. Dans ce contexte, les règles doivent être simples, peu nombreuses et pertinentes comme celles de cette petite liste qui peut, à mon avis, remplacer avantageusement toute méthode « hype » doctement assénée par des consultants grassement rémunérés.
- La règle de modularité et de composabilité dit que vous devez concevoir des microservices simples aptes à être composés avec d’autres et/ou chaînés à d’autres. Un microservice fait une seule chose mais la fait bien.
- La règle de séparabilité vous impose d’isoler les interfaces des moteurs. Les interfaces sont structurantes, pas la cuisine interne des microservices.
- La règle de représentation dit que vous devez concevoir des microservices pilotés par les données. Un tel microservice aura un fonctionnement plus simple, sera plus robuste et sa scalabilité sera meilleure.
- La règle de génération vous demande d’éviter de coder des choses répétitives et triviales et d’utiliser des programmes pour coder à votre place sans vous limiter à utiliser des fichiers WSDL pour engendrer le code de vos interfaces. Concevez vos propres DSL métier et utilisez des générateurs de lexers et de parsers pour les interpréter.
- La règle de diversité vous rend responsable de faire les choix technologiques et méthodologiques demandées en fonction du problème à résoudre et pas parce qu’il existe un catalogue logiciel ou une « stack » quelconque certifiés par un gourou d’entreprise. Si vous pensez que Python et REDIS sont parfaitement adaptés au microservice que vous voulez écrire et que vous le justifiez, ne tenez pas compte de l’avis de votre collègue qui vous tente de vous imposer Java et MongoDB parce que « ça se fait comme ça ici », il ne connaît vraisemblablement rien à votre problème.
- Enfin, la règle d’extensibilité vous impose de de prendre en compte les évolutions futures dans vos choix de conception. Lorsque vous concevez un schéma de représentation, pensez à ceux qui prendront votre suite et qui auront besoin d’y ajouter des attributs sans devoir modifier le code des microservices qui fonctionnent avec l’ancienne version.
Références
[1] Coburn Watson, Scott Emmons, and Brendan Gregg. A Microscope on Microservices. Techno Blog Netflix. 2015. (http://nflx.it/1S27wak)
[2] James Lewis, Martin Fowler. Microservices, a definition of this new architectural term. Blog personnel de M. Fowler. 2014. (https://martinfowler.com/articles/microservices.html)
[3] Jim Gray, Werner Vogels. A Conversation with Werner Vogels, learning from the Amazon technology platform. ACM Queue. Volume 4, issue 4. 2006. (https://queue.acm.org/detail.cfm?id=1142065)
[4] Nathan Marz, James Warren. Big Data: Principles and best practices of scalable realtime data systems. Manning Publications Co. 2015.
[5] Martin Fowler. CQRS. Command Query Responsibility Segregation. 2011. (https://martinfowler.com/bliki/CQRS.html)