Développer des microservices en 2019 et au-delà

L'architecture de microservices est en constante évolution. Apprenez les bonnes pratiques modernes pour bien procéder.

Sten Pittet Sten Pittet

« Microservices » est une pratique organisationnelle populaire et moderne d'ingénierie logicielle. Le principe directeur des microservices consiste à développer une application en divisant ses composants métier en plus petits services pouvant être déployés et exploités indépendamment les uns des autres. La séparation des préoccupations entre les services est définie comme des « limites de service ».

Les limites des services sont étroitement liées aux exigences métier et aux limites de hiérarchie organisationnelle. Les services individuels peuvent être liés à des équipes, des budgets et des feuilles de route distincts. Certains exemples de limites de service peuvent être les services de « traitement des paiements » et d'« authentification de l'utilisateur ». Les microservices diffèrent des pratiques de développement de logiciels héritées dans lesquelles tous les composants ont été regroupés.

Ce document fera référence à une start-up imaginaire appelée « Pizzup » afin d'illustrer l'application des microservices à une entreprise logicielle moderne.

Comment développer des microservices

Étape 1 : commencez par un monolithe

La première bonne pratique des microservices est que vous n'en avez probablement pas besoin. Si vous n'avez pas d'utilisateurs pour votre application, il y a de fortes chances que les exigences de l'entreprise changent rapidement lors du développement de votre produit minimum viable (MVP). Cela est simplement dû à la nature du développement de logiciels et au cycle de feedback qui doit avoir lieu pendant que vous identifiez les capacités métier clés que votre système doit fournir. Les microservices ajoutent un surcoût exponentiel et plus de complexité à gérer. C'est pourquoi il est beaucoup moins coûteux pour les nouveaux projets de conserver l'ensemble du code et de la logique dans une base de code unique, car il est plus facile de déplacer les limites des différents modules de votre application.

Par exemple, avec Pizzup, nous aurions pu commencer par une idée très simple du problème à résoudre pour nos clients, à savoir, que les personnes puissent commander des pizzas en ligne.

Un utilisateur de Pizzup disant, « En tant qu'utilisateur, je peux commander des pizzas en ligne ! »

En réfléchissant à la question de la commande de pizzas, nous allons devoir identifier les différentes capacités requises dans notre application afin de répondre à ce besoin. Nous devrons être en mesure de gérer une liste des différentes pizzas que nous pouvons préparer, nous devrons offrir aux clients le choix de commander une ou plusieurs pizzas, il faudra gérer le paiement, planifier la livraison, etc. On pourrait aussi laisser nos clients créer un compte dans le but de faciliter leur nouvelle commande la prochaine fois qu'ils utiliseront Pizzup. Et après avoir échangé avec nos premiers utilisateurs, on pourrait se dire que le suivi en direct de la livraison et l'assistance mobile nous donneraient définitivement un avantage sur la concurrence.

Image montrant la différence entre les utilisations de l'utilisateur final et de l'administrateur pour l'app Pizzup.

Ce qui était un simple besoin au départ se transforme rapidement en une liste de capacités que vous devez fournir.

Les microservices fonctionnent bien lorsque vous avez une bonne compréhension des rôles des différents services requis par votre système. Ils sont beaucoup plus difficiles à gérer si les exigences fondamentales d'une application sont toujours en cours d'élaboration. Il est en effet assez coûteux de redéfinir les interactions de service, les API et les structures de données dans les microservices, car il se peut que vous ayez beaucoup plus de pièces mobiles qui nécessitent d'être coordonnées. C'est pourquoi nous vous conseillons d'aller au plus simple jusqu'à ce que vous ayez reçu suffisamment de feedback d'utilisateurs pour être sûr que les besoins fondamentaux de vos clients sont compris et planifiés.

Il convient toutefois de faire preuve de prudence, car le développement d'un monolithe peut rapidement conduire à un code compliqué qu'il sera difficile de décomposer en plus petits composants. Essayez autant que possible d'identifier clairement les modules afin de pouvoir les extraire ultérieurement du monolithe. Vous pouvez également commencer par séparer la logique de votre interface web et faire en sorte qu'elle interagisse avec votre back-end via une API RESTful sur HTTP. Cela facilitera la future transition vers les microservices, lorsque vous commencerez à déplacer certaines ressources API vers différents services.

Étape 2 : organisez vos équipes de manière appropriée

Jusqu'à présent, il semblait que la mise en place de microservices était essentiellement une affaire technique. Il faudra diviser une base de code en plusieurs services, implémenter les bons modèles afin de limiter la casse et de récupérer après des problèmes de réseau, gérer la cohérence des données, surveiller la charge du service, etc. Vous devrez appréhender une série de nouveaux concepts, mais, surtout, restructurer l'organisation de vos équipes.

Toute organisation qui conçoit un système (défini au sens large) produira un design dont la structure est une copie de la structure de communication de l'organisation.

- Conway's Law

La loi de Conway est une réalité observable dans tous les types d'équipes. Si une équipe de développement est organisée avec une équipe back-end, une équipe front-end et une équipe opérationnelle distinctes, elles finiront par livrer des monolithes front-end et back-end distincts qui seront envoyés à l'équipe opérationnelle afin qu'elle puisse les gérer en production.

Ce type de structure ne répond pas correctement aux besoins des microservices, car chaque service peut être considéré comme son propre produit qui doit être livré indépendamment des autres. Au lieu de cela, vous devriez créer des équipes plus petites qui possèdent toutes les compétences nécessaires pour développer et maintenir les services dont elles sont responsables. Werner Vogels, DSI d'Amazon, a décrit cette situation avec l'expression « Vous le concevez, vous en êtes responsable ». Cette organisation des équipes présente des avantages importants. Tout d'abord, vos développeurs auront une meilleure compréhension de l'impact de leur code en production. Cela contribuera à produire de meilleures versions et réduira le risque de voir des problèmes livrés à vos clients. Ensuite, vos déploiements deviendront une seconde nature pour chaque équipe, car elles pourront travailler ensemble sur les améliorations du code ainsi que l'automatisation du pipeline de déploiement.

Étape 3 : fractionnez le monolithe pour développer une architecture de microservices

Lorsque vous avez identifié les limites de vos services et que vous avez compris comment transformer vos équipes pour plus de verticalité en termes de compétences, vous pouvez commencer à fractionner votre monolithe afin de créer des microservices. Voici les points clés à prendre en compte à ce moment-là.

Veillez à ce que communication entre les services reste simple grâce aux API REST

Si vous n'utilisez pas déjà une API REST, c'est le moment idéal pour l'adopter dans votre système. Comme l'explique Martin Fowler, vous souhaitez avoir « des points d'ancrage intelligents et des pipes stupides ». Cela signifie que le protocole de communication entre vos services doit être aussi simple que possible, et uniquement transmettre les données, sans les altérer. Toute la magie opèrera dans les points d'ancrage eux-mêmes, ils reçoivent une demande, la traitent et émettent une réponse en retour.

C'est aussi là que les microservices peuvent être distingués de la SOA en évitant la complexité de l'Enterprise Service Bus (ESB). Les architectures de microservice s'efforcent de maintenir la simplicité afin d'éviter un couplage étroit des composants. Dans certains cas, vous pouvez utiliser une architecture orientée événement grâce à des communications asynchrones basées sur des messages. Mais encore une fois, vous devriez vous pencher sur des services de file d'attente de messages de base tels que RabbitMQ et éviter d'ajouter de la complexité aux messages transmis sur le réseau.

Divisez votre structure de données

Il est relativement fréquent d'utiliser une base de données unique pour les différentes capacités dans un monolithe. Lorsqu'un utilisateur accède à sa commande, vous consultez directement le tableau utilisateur pour afficher les informations client, et ce même tableau peut être utilisé pour remplir la facture gérée par le système de facturation. Cela semble logique et simple, mais grâce aux microservices, il est préférable que les services soient découplés (afin que les factures restent toujours accessibles même lorsque le système de commande est en panne), parce qu'ils vous permettent d'optimiser ou de faire évoluer le tableau des factures indépendamment des autres. Cela signifie que chaque service peut finir par avoir sa propre base de données contenant les données dont il a besoin.

Cela introduit évidemment de nouveaux problèmes, car des données seront inévitablement dupliquées dans différentes bases de données. Dans ce cas, vous devriez viser la cohérence à terme et adopter une architecture orientée événements afin de faciliter la synchronisation des données entre plusieurs services. Par exemple, vos services de suivi de facturation et de livraison peuvent être à l'écoute des événements émis par le service de compte lorsqu'un client met à jour ses informations personnelles. À la réception de l'événement, ces services mettront à jour leur base de données en conséquence. Cette architecture orientée événement permet de maintenir la simplicité du service de compte, car elle n'a pas besoin de connaître tous les autres services dépendants. Elle indique simplement au système ce qu'elle a fait et les autres services écoutent et agissent en conséquence.

Vous pouvez également choisir de conserver toutes les informations relatives aux clients dans le service de compte et de ne conserver qu'une référence de clé étrangère dans votre service de facturation et de livraison. Ces derniers interagissent alors avec le service de compte pour obtenir les données pertinentes sur le client en cas de besoin, au lieu de dupliquer les enregistrements existants. Il n'y a pas de solutions universelles pour ces problèmes, et vous devrez examiner chaque cas spécifique pour déterminer la meilleure approche.

Développez votre architecture de microservices pour les pannes

Nous avons vu les grands avantages des microservices par rapport à une architecture monolithique. Ils sont plus petits et spécialisés ce qui les rend faciles à comprendre. Ils sont dissociés, ce qui signifie que vous pouvez remanier un service sans crainte d'endommager les autres composants du système ou de ralentir le développement des autres équipes. Ils offrent également plus de flexibilité à vos développeurs qui peuvent choisir différentes technologies si nécessaire sans être contraints par les besoins d'autres services.

En bref, disposer d'une architecture de microservices facilite le développement et la maintenance de chaque capacité métier. Mais les choses se compliquent lorsque vous regardez l'ensemble des services et la manière dont ils doivent interagir pour exécuter des actions. Votre système est maintenant distribué avec plusieurs points de défaillance, et vous devez y faire face. Vous devez prendre en compte non seulement les cas où un service ne répond pas, mais également être en mesure de gérer des réponses réseau plus lentes. La reprise d'activité suite à une panne peut également s'avérer difficile à certains moments, car vous devez vous assurer que les services qui sont remis en route ne sont pas saturés par les messages en attente.

Quand vous commencez à extraire des capacités de vos systèmes monolithiques, assurez-vous que vos designs sont conçus dès le début en pensant aux possibles défaillances.

Mettez l'accent sur la surveillance pour faciliter les tests de microservices

Les tests constituent un autre inconvénient des microservices par rapport à un système monolithique. Une application développée en tant que base de code unique n'a pas besoin de grand-chose pour que son environnement de test soit opérationnel. Dans la plupart des cas, vous devrez démarrer un serveur back-end couplé à une base de données pour pouvoir exécuter votre suite de tests.

Dans le monde des microservices, tout n'est pas aussi simple. Les tests unitaires seront encore assez similaires à ceux du monolithe et cela ne devrait pas vous sembler compliqué. Cependant, lorsqu'il est question d'intégration et de tests système, les choses se compliquent. Vous devrez peut-être démarrer plusieurs services à la fois, disposer de différentes bases de données opérationnelles, et votre configuration peut nécessiter des files d'attente de messages dont vous n'aviez pas besoin avec votre monolithe. Dans cette situation, effectuer des tests fonctionnels devient beaucoup plus coûteux, et le nombre croissant de pièces mobiles rend très difficile la prédiction des différents types de défaillances potentielles.

C'est pourquoi vous devrez mettre l'accent sur la surveillance pour pouvoir rapidement identifier les problèmes et pouvoir répondre en conséquence. Vous devrez comprendre les bases de référence de vos différents services et être en mesure de réagir non seulement lorsque ces services tombent en panne, mais aussi lorsqu'ils se comportent de façon inattendue. L'un des avantages de l'adoption d'une architecture de microservices est que votre système doit être résilient à une défaillance partielle. Par conséquent, si vous commencez à observer des anomalies dans le service de suivi de livraison de notre application Pizzup, ce ne sera pas aussi grave que s'il s'agissait d'un système monolithique. Notre application doit être conçue de manière à ce que tous les autres services répondent correctement et permettent à nos clients de commander des pizzas pendant que nous restaurons le suivi en direct.

Adoptez la livraison continue pour réduire les frictions de déploiement

Livrer manuellement un système monolitique en production est un effort fastidieux et risqué, mais réalisable. Bien sûr, nous ne recommandons pas cette approche et encourageons chaque équipe de développement à adopter la livraison continue pour tous les types de développement, mais au début d'un projet, vous pouvez effectuer vos premiers déploiements vous-même via la ligne de commande.

Cette approche n'est pas durable lorsque vous disposez d'un nombre croissant de services qui doivent être déployés plusieurs fois par jour. Par conséquent, dans le cadre de votre transition vers les microservices, l'adoption de la livraison continue est essentielle afin de réduire les risques d'échec de livraison, et de vous assurer que votre équipe se concentre sur le développement et l'exécution de l'application, plutôt que d'être bloquée sur son déploiement. Pratiquer la livraison continue signifie également que votre service a réussi les tests d'acceptation avant d'être déployé en production. Bien sûr, des bugs surviendront, mais au fil du temps, vous développerez une suite de tests robuste qui devrait augmenter la confiance de votre équipe dans la qualité des livraisons.

L'exécution de microservices n'est pas un sprint

Les microservices s'imposent rapidement comme une bonne pratique populaire et largement adoptée dans le secteur. Pour les projets complexes, ils offrent une plus grande flexibilité dans la façon dont vous pouvez développer et déployer des logiciels. Ils aident également à identifier et à formaliser les composants métier de votre système, ce qui est pratique lorsque plusieurs équipes travaillent sur la même application. Mais il y a également des inconvénients évidents à la gestion des systèmes distribués, et la division d'une architecture monolithique ne devrait être réalisée qu'en s'appuyant sur une solide compréhension des limites du service.

Le développement de microservices doit être considéré comme un parcours plutôt que comme un objectif immédiat pour une équipe. Commencez modestement pour comprendre les exigences techniques d'un système distribué, comment échouer de façon normale et faire évoluer des composants individuels. Ensuite, vous pouvez progressivement extraire de plus en plus de services à mesure que vous gagnez de l'expérience et des connaissances.

La migration vers une architecture des microservices n'a pas besoin d'être effectuée en un seul effort holistique. Une stratégie itérative de migration séquentielle de petits composants vers des microservices est plus sûre. Identifiez les limites des services les mieux définis au sein d'une application monolithique établie et travaillez de manière itérative pour les dissocier dans leur propre microservice.

Résumé

Pour récapituler, les microservices sont une stratégie qui est bénéfique à la fois pour le processus de développement de code technique brut et pour la stratégie globale d'organisation de l'entreprise. Les microservices aident à organiser les équipes en unités qui se concentrent sur le développement et la possession de fonctions métier spécifiques. Cette priorité granulaire améliore la communication et l'efficacité globales de l'entreprise. Des compromis sont nécessaires pour profiter des avantages des microservices. Les limites de service doivent être clairement définies avant de migrer vers une architecture de microservices. L'architecture de microservices est encore assez récente, mais c'est un moyen prometteur de développer des applications, et elle vaut la peine d'être étudiée. N'oubliez simplement pas que cela pourrait ne pas répondre aux besoins de votre équipe (pour l'instant).