Architecture Microservices – Les bonnes pratiques
Fabian Piau | lundi 3 octobre 2016 - 07:00Je travaille sur une architecture microservices depuis assez longtemps maintenant et j’ai assisté à pas mal de conférences sur le sujet. Dans cet article, je veux rassembler mon expérience pour vous donner quelques conseils basés sur mon retour d’expérience. Chaque titre représente ce que vous ne devez pas faire suivi d’une description de ce que vous devriez faire à la place. Même si vous faites déjà des microservices sur votre projet, cela peut tout de même être une bonne lecture (je l’espère) et un rafraîchissement de vos connaissances. Aussi, n’hésitez pas à commenter l’article et partager vos propres retours!
Vous pouvez appeler une architecture microservices SOA (ou appelez la comme bon vous semble), mais je préfère utiliser le mot à la mode pour le référencement de mon blog. ;)
L’adoption d’une architecture microservices n’est pas si aisée. Ecrire des microservices, ce n’est pas seulement coder, c’est également un bouleversement des équipes et de la structure de l’organisation.
Extrait moi donc tout ce code complexe, il sera plus simple dans un microservice!
L’adoption est toujours un peu plus facile lorsqu’elle commence sur un nouveau projet, quand on démarre de zéro. Si tel est votre cas, alors vous êtes chanceux! Si vous voulez casser une application monolithique en un ensemble de microservices, évitez de le faire en une seule fois, la manière big bang est le meilleur moyen de faire tout sauter. Essayez de regrouper l’ensemble des fonctionnalités similaires qui peuvent être inclus au sein d’un microservice. Assurez-vous que toutes ces fonctionnalités soient bien testées avant d’engager votre refactoring. Si la couverture de code par les tests est faible, vous devez commencer par l’écriture de tests. Cela vous permettra non seulement d’améliorer le code du monolithe, mais vous réutiliserez ainsi ce code dans votre futur microservice. Gardez à l’esprit que vous préparez l’extraction, ce travail ne sera donc pas inutile.
Si vous ne disposez pas suffisamment de tests, vous allez faire les choses à l’aveuglette et vous ne serez plus en mesure de vous assurer que vous n’avez rien cassé, la régression est la dernière chose que vous voulez. Pour résumer, écrivez des tests d’intégration autour du code que vous voulez extraire, ensuite vous pouvez commencer à extraire la fonctionnalité pour créer un microservice. Vos tests d’intégration devront toujours être au vert, mais la plomberie derrière aura changé.
Commencez petit et facile au début. Et n’ayez pas peur de dupliquer du code. Une fois que votre microservice est branché et fonctionne bien, vous pouvez supprimer le code dupliqué du monolithe. Faire de l’A/B testing peut aussi aider, vous pouvez rerouter progressivement le trafic vers le nouveau microservice et voir comment le système réagit.
Le piège du ‘micro-monolith-service’
Gardez le microservice assez petit et pas trop complexe. Il n’y a pas de réponse magique à la question combien de lignes de code pour un service pour être considéré comme un microservice? Cela se base plus sur le ressentiment et des bases techniques saines avec un design bien construit. Si un nouveau membre de l’équipe a besoin d’une journée pour comprendre le code et ce que votre microservice fait, alors vous avez probablement un souci. Quelques heures tout au plus devraient largement suffire.
Vous devez parser un fichier XML, laissez-moi écrire un microservice pour ça!
C’est l’autre extrême, il faut éviter de créer un microservice quand une simple librairie est suffisante. Vous ne voulez pas avoir une profondeur d’appel trop grande (nombre de requêtes imbriquées). Si vous avez plus de 3 requêtes imbriquées, vous faites probablement des nanoservices. Un appel implique une latence réseau et une éventuelle défaillance. Si un microservice doit être déployé avec une API, une librairie sera directement intégrée sans surcoût opérationnel. Il y a donc un équilibre entre coût DevOps et complexité monolithique.
Désolé, je ne peux pas vous aider, je n’ai pas travaillé sur ce microservice…
Il est impossible que toutes les équipes soient responsables de tous les microservices. La synchronisation et la communication entre vos équipes devient primordiale. J’ai très souvent entendu le fait qu’une équipe ne devrait pas être responsable d’un microservice, et que tout le monde devrait être en mesure de changer et de travailler sur n’importe quel microservice. Eh bien, en réalité ce n’est pas vraiment le cas! Néanmoins, vous pouvez adopter quelques bonnes pratiques pour rendre ce concept plus facile.
A mon avis, une équipe responsable d’un microservice signifie qu’elle est responsable de la construction et de la bonne exécution. Quand quelque chose coince en production, l’équipe devrait être le principal point de contact. Vous aurez juste à trouver une personne au sein de l’équipe qui acceptera un appel à 5h du matin… Je plaisante ;)
Spring, Dropwizard, Finagle, Clojure, faisons un mix de tout ça!
Essayez de vous limiter à une stack technologique commune pour tous vos microservices. Je sais qu’un grand avantage des microservices est que vous pouvez les construire avec n’importe quel langage ou technologies. Mais les développeurs passionnés auront tendance à utiliser le dernier framework à la mode. Dans le long terme et avec l’inévitable turn-over, la maintenance de vos microservices va devenir douloureuse et finir en cauchemar. Le passage de microservices entre les équipes va devenir très difficile, voire impossible. C’est pour cela, je pense qu’il est important de se limiter à un certain nombre de technologies.
De nombreuses technologies sont disponibles pour faire des microservices, vous devez utiliser quelque chose de fiable, maintenu, et ainsi de suite. Quelle sérialisation, texte ou binaire? REST, Thrift, SOAP? Solution open-source ou du fait-maison? Il n’y a pas de bonne réponse. Comparez les différentes technologies, les avantages et les inconvénients, et utilisez la solution la plus appropriée pour votre besoin.
Dans le même temps, cette limitation ne devrait pas vous empêcher d’innover. Ne vous limitez pas à ce que vous utilisez et maîtrisez déjà si vous pensez qu’il y a une meilleure solution sur le marché. Essayez-la sur un nouveau microservice (de préférence non critique pour le système) avec un POC, puis en cas de succès, vous pouvez la propager à l’ensemble du système.
Qu’en est-il du stockage des données?
Vous pouvez être plus flexible sur le type de data store. C’est une dépendance externe, et elle doit être adaptée en fonction des besoins (base de données relationnelle, noSQL, en mémoire, accès en lecture seule…).
Gardez vos data stores indépendants, chaque microservice est un gardien de ses données. Vous ne devriez jamais le contourner en liant un microservice au data store d’un autre. Ce serait une très mauvaise conception. Dans le cas où vous êtes limité à une base de données, ça ne posera pas de problème car vous pouvez définir différents schémas pour chaque microservice afin de limiter les accès.
Si un microservice tombe, tout mon système est en panne…
Il est important de concevoir une bonne architecture, c’est le socle technique qui supportera vos microservices. Pensez scalabilité, circuit breaker (pattern coupe circuit), service discovery (découverte des services) dès le début du projet, surtout pas un mois avant de mettre en production avec des milliers d’utilisateurs potentiels. Vous devez toujours garder cela à l’esprit à partir du jour 0. Certaines personnes peuvent argumenter sur ce point, et parfois vous pourriez avoir à convaincre le product owner qui ne voit pas de valeur business. Mais croyez-moi, plus vous attendez, plus vous aurez du pain sur la planche, et vous ne serez pas si confiant avec votre architecture tant que toutes ces tâches techniques ne seront pas complètes.
Vous avez affaire à de nombreuses requêtes distribuées sur le réseau, qui n’est généralement pas très fiable, il faut imaginer un design résilient. Ceci est un état d’esprit que l’équipe se doit d’adopter. Vous devez penser aux retries (plusieurs tentatives, en cas d’échec, on réessaie sur une autre instance par exemple), aux appels idempotents et ainsi de suite. Mettre en place des retries n’est pas aussi facile que de l’expliquer, surtout quand l’appel n’est pas idempotent. A un certain point, vous aurez besoin d’affiner la configuration des tentatives. Par exemple, éviter des retries au niveau inférieur si on réessaie déjà au niveau supérieur, ou bien adapter le délai d’attente entre les tentatives au niveau supérieur, celui-ci devrait être plus grand que le délai d’attente au niveau inférieur, etc. Tous vos microservices devraient exposer un ensemble d’URL de type healthchecks et exposer des métriques utiles qu’un outil de surveillance pourra utiliser (suivant un modèle basé sur des pull ou des push).
Il est également important de tester l’infrastructure. Qu’est-ce qui se passera si cette instance de microservice tombe? Vous devriez faire du Monkey testing (c.-à-d. éteindre certains microservices au hasard), le système devrait réagir positivement et être encore capable de traiter et de servir les requêtes (en mode dégradé).
Ok, je vais faire un ‘grep’ sur la log pour voir ce qui s’est passé hier… Attend… Il y a 50 logs ici!
Avoir beaucoup de microservices implique beaucoup d’interactions, et donc pas mal de log. Il sera difficile de déboguer si quelque chose a mal tourné en production. Où est-ce que ça s’est passé? Quel fichier de log? Quel environnement? Les ops peuvent se perdre facilement… Essayez de faire un logging précis, surtout gardez un identifiant de corrélation pour être en mesure de retracer la pile d’appel à travers les différents fichiers de log.
Soyez proactif et n’attendez pas que le client se plaigne. Traquez toute stacktrace qui se produit, déclenchez des alertes et corrigez constamment tous les problèmes jusqu’à ce que vos logs soient « nettoyées » et contiennent uniquement des informations utiles et précises. La mise en place d’un outil de surveillance des logs (par exemple Splunk, Kibana…) aura du sens dans une architecture microservices.
Nous ne pouvons pas tester, nous dépendons d’un microservice pas encore prêt
Lors de l’écriture des tests d’intégration, mockez toutes les dépendances externes au microservice. Surtout si une équipe est encore en train de développer un microservice dont vous dépendez. Vous ne voulez pas attendre qu’ils aient fini. Par exemple, vous pouvez jeter un oeil à Wiremock. A l’opposé, pour les tests de bout en bout (end-to-end), essayez de fournir des outils efficaces et rapides pour les exécuter localement, l’utilisation de serveurs embarqués est une bonne façon d’y parvenir.
Je l’ai déjà mentionné, déployez souvent en production, surtout au début, et n’attendez pas 6 mois pour déployer vos microservices. Même s’ils ne sont pas (encore) utiles d’un point de vue business, vous serez davantage en confiance lorsque vous travaillerez dessus, et alors vous pourrez implémenter la logique métier sans trop vous soucier de l’architecture. Plus tôt vous testez en production, plus tôt vous pourrez déceler des bogues et être en mesure de les corriger rapidement.
Nous allons concevoir l’API comme ça pour l’instant, au plus simple, nous la changerons plus tard
Il est difficile d’être agile lors de la conception d’une API, surtout quand celle-ci est publique. Une fois qu’une version a été livrée et commence à être utilisée, tout changement conséquent impliquera un changement de contrat de l’API. Vous allez alors devoir versionner votre API et, finalement, devoir maintenir plusieurs versions en parallèle. Il est important de passer un peu de temps au début pour définir un bon contrat pour l’API qui sera capable d’évoluer sans breaking changes (idéalement). Cela semble Waterfall pour vous? Eh bien, c’est probablement un peu le cas…
Toujours garder à l’esprit qu’il faut être rétrocompatible (même si les appels sont internes) et utiliser l’API versioning. Par exemple, ne pas renommer un champ comme ça, mais suivre un processus un peu plus long mais sûr, vous devez ajouter le nouveau champ, gardez l’ancien déprécié jusqu’à ce que tous les clients soient migré, ensuite vous pouvez le supprimer.
Les ops ont besoin d’une journée pour déployer quelques microservices, comment est-ce possible?!
L’équipe opérationnelle doit changer de mentalité, au lieu de déployer et surveiller une grosse et unique application, ils devront gérer de nombreuses petites applications et s’assurer qu’il n’y a pas de problème de communication. De quelques étapes manuelles, l’équipe se retrouve avec de nombreuses étapes fastidieuses. Ils seront rapidement dépassés s’ils gardent les mêmes habitudes, en particulier quand on sait que le nombre de microservices va croître au fil du temps.
Ils auront besoin d’automatiser leur travail et éviter toute étape manuelle sujette à des erreurs humaines. L’adoption d’une architecture microservices vous oblige à faire du « vrai » DevOps. Le développeur doit participer à la mise en place de l’architecture, il n’est plus suffisant de donner un fichier et compter sur les ops pour faire le travail. Les développeurs doivent être impliqués. Idéalement, un ops sera dédié à l’équipe pour s’assurer que la configuration réseau est correcte, les health checks existent et fonctionnent, il participera aux stand-ups et pourra même avoir son bureau avec l’équipe de dév. Il sera sans doute nécessaire de commencer à utiliser un outil de déploiement automatisé tel qu’Ansible.
Nous avons passé 6 mois sur cette architecture, elle fonctionne super bien… Par contre on a 50 utilisateurs!
Dernier conseil, ne pas essayer de tout faire en même temps. Vous devez déjà gérer pas mal de problématiques. Vous pouvez mettre de côté certains points comme le self-healing (auto-guérison), le service discovery (découverte de service), circuit-breaker (coupe-circuit), auto-scaling, etc. L’utilisation de retries et de multiples instances sera suffisant au début. Mais gardez à l’esprit que vous devrez les mettre en oeuvre à un moment donné, surtout quand votre système sera une réussite et sera utilisé par des milliers d’utilisateurs.
Commentaires récents