Du monolithe aux microservices : quand la décomposition aide vraiment

Du monolithe aux microservices : quand la décomposition aide vraiment
Le mot « monolithe » est devenu une insulte dans les conversations d'ingénierie logicielle. Dites que vous tournez sur un monolithe et la moitié de la salle vous regardera avec la sympathie habituellement réservée aux gens qui utilisent encore le fax.
C'est absurde. Certaines des entreprises logicielles les plus prospères au monde font tourner des monolithes — ou en ont fait tourner pendant des années avant de décomposer, et n'ont décomposé que des pièces spécifiques qui le nécessitaient vraiment.
Shopify traite 6,9 milliards de dollars en ventes marchandes par jour avec un monolithe. Stack Overflow a servi 100 millions de visiteurs mensuels sur ce qu'ils appelaient affectueusement « un monolithe dont on prend bien soin ».
La question n'a jamais été « monolithe ou microservices ? » C'était toujours « où sont les frontières, et lesquelles doivent être indépendantes ? »
Pourquoi les monolithes sont en fait bons
Un monolithe est une seule unité déployable où tout votre code applicatif vit. Un dépôt, un pipeline de build, un déploiement. Et cette simplicité porte de vrais avantages :
Facile à raisonner. Quand un bug survient, vous cherchez dans un seul codebase. Vous n'avez pas besoin d'un système de traçage distribué pour comprendre pourquoi une commande a échoué.
Opérations simples. Un processus à surveiller. Un déploiement à gérer. Une base de données à sauvegarder.
Le refactoring est bon marché. Renommez une fonction et votre IDE trouve chaque site d'appel. Déplacez un module entre packages avec confiance.
Les transactions sont faciles. Quand votre service de commande, votre service d'inventaire et votre service de notification vivent tous dans le même processus, les transactions ACID fonctionnent tout simplement. Dans un monde de microservices, vous avez besoin de sagas, de cohérence éventuelle et de transactions de compensation.
Quand le monolithe commence à faire mal
Couplage de déploiement. Quand votre équipe dépasse 15-20 ingénieurs, coordonner les déploiements devient un exercice de négociation.
Granularité de mise à l'échelle. Vous ne pouvez mettre à l'échelle un monolithe qu'en faisant tourner plus de copies de la totalité. Si votre module de traitement d'images a besoin de 10x plus de CPU que votre module d'auth, vous payez 10x plus pour de la capacité auth dont vous n'avez pas besoin.
Rayon de blast. Une fuite mémoire dans un module peut faire tomber l'application entière.
Si vous rencontrez ces problèmes — et vérifiez que c'est vraiment le cas, pas seulement les craindre — alors la décomposition sélective commence à avoir du sens.
Le pattern Strangler Fig
La stratégie de migration la plus sûre emprunte son nom à la botanique. Un figuier étrangleur germine dans la canopée d'un arbre hôte, envoyant des racines vers le sol. Sur des décennies, ses racines s'épaississent et finissent par remplacer l'arbre hôte — mais l'hôte continue de fonctionner tout au long du processus.
Appliqué au logiciel :
- Identifiez un contexte borné dans votre monolithe avec des entrées et sorties claires.
- Construisez le nouveau service à côté du monolithe. Sa propre base de données, son propre pipeline de déploiement.
- Routez le trafic progressivement. Commencez par envoyer 1% des requêtes au nouveau service. Surveillez. Augmentez à 10%, puis 50%, puis 100%.
- Supprimez l'ancien code du monolithe seulement après que le nouveau service a tourné en production pendant des semaines sans problèmes.
Nous avons utilisé exactement cette approche avec la plateforme e-commerce d'un client. Temps d'arrêt total pendant la migration : zéro.
Ce que la plupart des équipes font mal
Extraire trop de services trop vite
Le mode d'échec le plus courant est une équipe qui lit sur les microservices, s'enthousiasme, et décompose leur monolithe en 30 services en un trimestre. Soudain, ils ont 30 dépôts, 30 pipelines CI, 30 choses à surveiller, et un système distribué qu'ils n'ont pas la maturité opérationnelle de gérer.
Ignorer le réseau
Les appels de fonction dans un monolithe prennent des microsecondes. Les appels réseau entre services prennent des millisecondes — 1000x plus lent. Et les appels réseau peuvent échouer de manières que les appels de fonction ne peuvent pas.
Créer un monolithe distribué
Si vos microservices doivent être déployés ensemble, partagent une base de données, ou ne peuvent pas fonctionner quand l'un d'eux est en panne, vous n'avez pas des microservices. Vous avez un monolithe distribué — ce qui vous donne la complexité des microservices avec le couplage d'un monolithe. Le pire résultat possible.
Un chemin intermédiaire : le monolithe modulaire
Structurez votre monolithe avec des frontières de module strictes. Chaque module a son propre répertoire, ses propres types, son propre schéma de base de données. Les modules communiquent à travers des interfaces définies.
C'est beau : si vous construisez un monolithe modulaire correctement, extraire un module en service plus tard est simple, parce que les frontières sont déjà propres.
Le conseil honnête
Si vous construisez quelque chose de nouveau, commencez par un monolithe modulaire. Décomposez quand et où vous avez des preuves que vous en avez besoin, pas où vous imaginez que vous pourriez.
Si quelqu'un vous dit que les monolithes sont dépassés ou non professionnels, demandez-lui d'expliquer le coût opérationnel de la gestion de 40 microservices. Le silence est instructif.