Quentin Boisson
Mon parcoursAccueil

Architecture Microservices

ACTAriane

L'architecture d'une application informatique détermine la manière dont ses différents composants sont organisés, communiquent entre eux et sont orchestrés afin de répondre au mieux aux besoins métier tout en maintenant la cohérence technique de l'application sur le long terme. Ce choix architectural a des répercussions profondes sur la maintenabilité, l'évolutivité et la robustesse de l'application. Une architecture mal conçue peut faire accumuler une dette technique telle aux développeurs que le projet n'en est simplement plus maintenable et doit envisager une nouvelle version avec une refonte complète de l'organisation du code. Les décisions architecturales ont donc énormément de poids, raison pour laquelle elles font dans l'entreprise où j'ai effectué mon alternance l'objet de longues réunions de conceptions afin d'être sûrs de partir sur des bases solides.

Il existe actuellement deux approches architecturales majeures dont les paradigmes s'opposent clairement : l'architecture monolithique et l'architecture microservices, chacune répondant à des contextes de développement et des contraintes différents.

La plupart de mes projets informatiques jusqu'à récemment utilisaient une architecture monolithique, où l'ensemble de l'application forme un bloc unique et indivisible. Concrètement, cela signifie que toutes les fonctionnalités de l'application, que ce soit l'authentification des utilisateurs, la synchronisation des données de l'application avec des API externes, le traitement des paiements, l'envoi d'emails ou encore la génération de documents cohabitent dans une seule et même base de code, partagent la même base de données, et sont déployées ensemble comme une unité.

Cette approche présente des avantages indéniables et assez évidents pour débuter un projet : elle est simple à comprendre, rapide à mettre en place, et ne nécessite pas de gérer la complexité de communications entre services distincts. C'est pour ces raisons que lors de mes projets étudiants en partenariat avec des entreprises, au vu du scope relativement restraint des projets en question, ne s'insérant pas forcément dans une architecture multi-applications plus globale, l'architecture monolithique était la solution qui semblait la plus appropriée. Cependant, au fur et à mesure qu'une application grandit, et qu'un service de développement est amené à travailler sur de nombreux projets dont les fonctionnalités peuvent parfois se recouper, le monolithe révèle ses limites. Modifier une petite fonctionnalité nécessite de redéployer l'application entière, une erreur dans un module peut faire tomber toute l'application, et plusieurs équipes travaillant simultanément sur le même code génèrent inévitablement des conflits et des ralentissements. De plus, dans le cas du développement simultané de plusieurs applications, on en arrivera parfois à réinventer plusieurs fois la roue. Il n'est par exemple pas toujours nécessaire d'avoir une gestion de l'authentification ou des paiements différentes au sein d'un même parc applicatif, et on pourrait vouloir centraliser ces fonctionnalités afin de gagner en temps de développement et en robustesse.

C'est dans ce contexte que mon tuteur a estimé pertinent, lors de mon alternance, d'introduire des microservices dans l'architecture de nos différentes applications. L'architecture microservices adopte une philosophie radicalement différente : plutôt qu'un unique bloc monolithique, l'application est décomposée en petits services indépendants, comme on découperait un code en fonction élémentaires et réutilisables, chacun responsable d'un domaine métier spécifique et pouvant être développé, déployé et mis à l'échelle séparément. Imaginons une application de commerce en ligne : au lieu d'un monolithe gérant tout, on aurait un service dédié au catalogue produits, un autre pour la gestion des utilisateurs, un troisième pour le panier et les commandes, un quatrième pour les paiements, et ainsi de suite. Notre premier applicatif à bénéficier de cette transition fut ACT, notre gestionnaire d'actes fonciers et juridiques. Cette application impliquant la plupart des services métier de l'entreprise, elle a rapidement gagné en volume, et il a fini par devenir évident que nous gagnerions à isoler certaines fonctionnalités dans leurs propres microservices qui pourraient alors fonctionner en autonomie et être réutilisés dans d'autres applications, comme pour notre microservice de publipostage qui est aujourd'hui largement utilisé au sein de notre parc applicatif.

Dans une architecture microservices, chaque service possède sa propre base de données, son propre cycle de vie, et communique avec les autres via des API bien définies, généralement en HTTP/REST, ou via des systèmes de messagerie asynchrone pour des traitements particulièrement longs et coûteux. L'intérêt principal des microservices réside donc dans l'autonomie qu'ils confèrent aux équipes et aux services eux-mêmes. Lorsqu'une équipe travaille sur le service de paiement, elle peut choisir la technologie et les méthodes de traitement les plus adaptées à ce problème spécifique (tant qu'elles respectent les standards de codes mis en place par le Tech Lead en collaboration avec ses équipes), modifier son code, et le déployer en production sans impacter les autres services ni attendre qu'une autre équipe termine ses développements de peur de générer des conflits.

Cette indépendance se manifeste également au niveau de la scalabilité : par exemple, si le service de gestion des paiements subit une forte charge pendant une période spécifique, en raison de renouvellement de paiements périodiques, ou pour une application d'e-commerce pendant la période des soldes, on peut augmenter uniquement ses ressources sans avoir à dupliquer l'ensemble de l'application comme on le ferait avec un monolithe. Cette granularité dans la gestion des ressources permet des économies substantielles et une meilleure réactivité face aux variations de charge, augmentant ainsi considérablement l'adaptabilité de l'application face aux facteurs externes.

La résilience est sans aucun doute un autre avantage majeur de cette architecture. Dans un monolithe, une erreur critique dans n'importe quel module peut potentiellement faire tomber l'application entière, privant tous les utilisateurs de l'ensemble des services que celle-ci propose. Avec les microservices, un problème dans le service de publipostage n'empêchera pas les utilisateurs de consulter des actes juridiques ou de mettre à jour les informations de leurs parcelles, elle bloquera uniquement la génération et l'envoi de documents. Le système reste partiellement opérationnel même en cas de défaillance, ce qui améliore considérablement l'expérience utilisateur et la disponibilité globale de l'application. Cette isolation des pannes est possible grâce à des patterns comme le circuit breaker, qui détecte automatiquement qu'un service ne répond plus correctement et évite de continuer à l'appeler inutilement, permettant au reste du système de continuer à fonctionner.

Cependant, l'architecture microservices n'est pas une solution miracle et introduit sa propre complexité, souvent sous-estimée. La communication entre services devient un enjeu central : là où dans un monolithe un appel de fonction suffit, il faut maintenant passer par le réseau, ce qui implique de gérer la latence, les timeouts, les erreurs réseau, et la sérialisation/désérialisation des données. Cette communication peut se faire de manière synchrone via des API REST, où un service attend la réponse d'un autre avant de continuer, ou de manière asynchrone via des systèmes de messagerie comme RabbitMQ ou Kafka, où les services s'échangent des messages sans attendre de réponse immédiate. L'asynchrone apporte plus de découplage et de résilience, mais complexifie le raisonnement sur le flux de données et rend plus difficile le débogage des problèmes sur des traitements effectués en background de l'application. Cette méthode peut toutefois s'avérer nécessaire pour des traitements particulièrement longs à effectuer, où on ne peut pas se permettre d'attendre une réponse synchrone de la part d'une autre partie de l'application pendant plusieurs minutes.

La gestion de la cohérence des données constitue également un défi majeur dans les architectures microservices. Dans un monolithe avec une base de données unique , maintenir la cohérence est relativement simple grâce aux transactions ACID qui garantissent que plusieurs opérations se déroulent entièrement ou pas du tout. Dans un système distribué où chaque service possède sa propre base de données, cette garantie n'existe plus au niveau global. Si une commande sur un site d'e-commerce nécessite de débiter le compte de l'utilisateur ET de réserver le stock du produit ET d'enregistrer la commande, comment garantir que ces trois opérations réussissent toutes ou échouent toutes ensemble ? On est alors obligés de recourir à des patterns plus complexes et plus coûteux à mettre en place, comme les sagas, qui décomposent une transaction métier en une séquence d'étapes locales avec des compensations définies en cas d'échec. C'est certainement moins simple qu'une transaction classique, mais c'est le prix à payer pour bénéficier de l'indépendance des services offerte par cette architecture.

L'observabilité devient également une problématique cruciale dans un environnement microservices. Comprendre ce qui se passe dans un monolithe est relativement simple : on consulte les logs d'une seule application, et tous les détails qu'on cherche s'y trouvent, rassemblés au même endroit. Avec des applications massives pouvant faire appel à des dizaines de microservices, une requête utilisateur peut traverser cinq ou six services différents, générant autant de logs dispersés. On ne peut alors plus se contenter de suivre ces logs de façon naïve. Pour y voir clair, il faut mettre en place du distributed tracing, qui permet de suivre le parcours complet d'une requête à travers tous les services qu'elle traverse, en attribuant à cette dernière un identifiant unique qui garde son intégrité lorsqu'il se propage de service en service. Des outils comme Jaeger ou Zipkin permettent de visualiser ces traces et d'identifier rapidement où se situent les ralentissements ou les erreurs. De même, les métriques et le monitoring doivent être centralisés pour avoir une vue d'ensemble de la santé du système. Chez Valeco, par exemple, tous les microservices doivent implémenter un endpoint de santé permettant à tout moment de tracer l'état de chacun, les erreurs générées, et les pannes partielles ou complètes qui pourraient les affecter via une interface centralisée afin de faciliter leur dépannage.

Le déploiement et l'orchestration des microservices nécessitent également des outils spécialisés. Déployer manuellement des dizaines de services sur différents serveurs devient rapidement ingérable. C'est là qu'interviennent des technologies comme Docker et Kubernetes. Docker permet d'empaqueter chaque service avec toutes ses dépendances dans un conteneur isolé qui s'exécutera de manière identique quel que soit l'environnement. S'il s'agit d'une technologie déjà utile pour gérer des applications monolithiques, ce genre de système de conteneurisation devient pour ainsi dire essentiel quand on travaille avec des microservices. Kubernetes, quant à lui, a pour charge d'orchestrer intelligemment ces conteneurs : il décide sur quels serveurs les déployer, gère leur mise à l'échelle automatique en fonction de la charge, redémarre automatiquement les conteneurs qui tombent en panne, et gère le routage du trafic entre les différentes instances. Il existe bien entendu d'autres outils similaires pouvant remplir ces rôles, mais quelque soient les solutions techniques exactes qu'on utilise, ces outils transforment ce qui serait autrement un cauchemar opérationnel en un système gérable et automatisé.

Quand l'architecture microservice atteint une certaine taille, il peut être intéressant d'utiliser une API Gateway comme point d'entrée unique d'une architecture microservices. Plutôt que d'exposer directement tous les microservices au client (le navigateur web de l'utilisateur ou l'application mobile, par exemple), l'API Gateway agit comme un intermédiaire qui va venir router les requêtes vers les services appropriés. Elle est également capable d'agréger les réponses de plusieurs services pour répondre à une requête complexe, de gérer l'authentification de manière centralisée, et peut appliquer des politiques de rate limiting pour protéger les services d'une surcharge ponctuelle qui pourrait nuire à leurs performances. Cela simplifie grandement le développement des clients qui n'ont qu'un seul point de contact plutôt que de devoir connaître l'emplacement exact de chaque service dans des flottes de microservices très conséquentes.

Sur le plan organisationnel, l'architecture microservices s'accompagne généralement d'une réorganisation des équipes selon le principe de "vous le construisez, vous le gérez" (you build it, you run it). Chaque équipe devient propriétaire de bout en bout d'un ou plusieurs services, de leur développement à leur déploiement et leur maintenance en production. Cette responsabilité complète favorise la qualité du code et la réactivité face aux incidents, puisque l'équipe qui connaît le mieux le service est aussi celle qui intervient en cas de problème. Un microservice étant moins volumineux qu'une application monolithe complète, il est plus aisé pour un développeur ou une équipe de développeurs d'en conserver la charge et la responsabilité dans le temps. Ayant développé le microservice de publipostage de Valeco, j'en suis par exemple le responsable de facto lorsqu'il faut le faire évoluer, répondre à des questions techniques le concernant, ou effectuer une maintenance dessus. Cela contraste avec l'approche traditionnelle où les développeurs se passent plus régulièrement le relais entre eux pendant le développement ou transmettent la responsabilité du produit à une équipe d'exploitation distincte, créant parfois des frictions et des incompréhensions.

L'expérience de transition d'une architecture monolithique vers des microservices m'a permis de comprendre que ce changement n'est pas seulement technique, mais également organisationnel. Il ne s'agit pas simplement de découper du code en morceaux plus petits, mais de repenser fondamentalement la manière dont les équipes collaborent, dont les applications sont conçues et exploitées, et dont on aborde les questions de résilience, de scalabilité et de maintenance. Même si c'était globalement la bonne solution à mettre en place pour les projets sur lesquels cette architecture a été intégrée, cette transition m'a confronté à des problématiques réelles de systèmes distribués. Si l'échelle des microservices au sein de Valeco reste encore assez raisonnable pour que ces problématiques restent gérables sans moyens techniques trop lourds à mettre en place, cette transition m'a poussé à m'intéresser à des problématiques telles que la cohérence éventuelle, la gestion des échecs partiels ou le monitoring distribué, thématiques qui sont globalement absentes des architectures monolithiques mais deviennent centrales dès lors qu'on embrasse les microservices et qu'on se préoccupe de leur évolution à long terme. Cette expérience m'a appris qu'il n'existe pas d'architecture universellement supérieure, mais plutôt des compromis à évaluer en fonction du contexte spécifique de chaque projet.

Pour me contacter :

quentin.boisson@hotmail.com

Mon profil