← Retour aux articles
Défis

Du Monolithe aux Microservices : Guide Pratique pour les CTOs de Startups en Croissance

Par Marc Molas·1 décembre 2024·10 min de lecture

Votre monolithe vous a amené jusqu'ici. Il a permis à une petite équipe de livrer vite, de déployer avec un seul pipeline et de débugger avec un stack trace au lieu de traquer des logs à travers cinq services différents. Ça a fonctionné. Et si vous avez suivi les principes d'un monolithe modulaire bien conçu, il vous a donné une base solide.

Mais maintenant quelque chose a changé. Vous avez trois équipes qui travaillent sur le même dépôt et les conflits de merge sont constants. Le module de traitement des paiements doit scaler différemment de celui des notifications. Un déploiement qui devrait prendre dix minutes en prend une heure parce qu'il faut coordonner entre les équipes. Un bug dans le module de reporting bloque la release d'une fonctionnalité de facturation qui est prête depuis une semaine.

Ce sont des signaux réels. Et la réponse n'est pas "réécrivons tout en microservices ce trimestre". La réponse est d'évoluer graduellement.

Quand est-ce vraiment le moment de migrer

Vous ne migrez pas parce que les microservices sont à la mode. Vous migrez parce que le monolithe est devenu un goulot d'étranglement pour votre business. Voici les signaux concrets :

Plusieurs équipes se marchent dessus. Deux équipes touchent le même code, les PRs se bloquent mutuellement, et chaque merge nécessite une coordination qui consomme plus de temps que le développement lui-même. La loi de Conway commence à faire effet : votre architecture doit refléter votre organisation.

Des besoins de scaling divergents. Votre API de recherche doit scaler horizontalement avec le trafic utilisateur, mais votre service de traitement de fichiers a besoin de machines avec plus de RAM et de CPU. Vous scalez le monolithe entier pour satisfaire le composant le plus exigeant, en payant de l'infrastructure dont le reste n'a pas besoin.

Des déploiements trop longs et risqués. Un changement de trois lignes nécessite de déployer toute l'application. Le pipeline de CI prend 45 minutes. Et chaque déploiement est un événement qui génère de l'anxiété parce que n'importe quelle partie du système peut casser.

Une panne casse tout. Un memory leak dans le module de reporting fait tomber l'API clients, le dashboard d'administration et le système de facturation. Il n'y a pas d'isolation des pannes.

Si vous reconnaissez deux ou plus de ces signaux, il est temps de planifier la migration. Si vous n'en voyez qu'un, vous pouvez probablement résoudre le problème avec une meilleure modularisation interne.

Le Strangler Fig Pattern : ne réécrivez pas, extrayez

La métaphore vient du figuier étrangleur, une plante qui pousse autour d'un arbre existant jusqu'à le remplacer complètement. Votre monolithe est l'arbre. Les nouveaux services sont le figuier. Et la clé est que l'arbre reste vivant pendant que le figuier pousse.

Vous ne réécrivez pas le monolithe. Vous extrayez des services un par un, graduellement, pendant que le monolithe continue de fonctionner en production. Chaque extraction est un pas petit et contrôlé. Si quelque chose tourne mal, le monolithe est toujours là comme fallback.

Étape 1 : Identifiez les frontières

Avant d'extraire quoi que ce soit, vous avez besoin d'une carte claire des modules de votre monolithe et de leurs dépendances. Si vous avez conçu un monolithe modulaire avec des bounded contexts bien définis, cette partie est plus simple. Sinon, c'est le moment de le faire.

Cherchez les coutures naturelles : des modules qui communiquent avec le reste via des interfaces claires, qui ont leur propre modèle de données, qui représentent un domaine métier distinct. En termes de Domain-Driven Design, vous cherchez des bounded contexts.

Dessinez le graphe de dépendances. Identifiez quels modules dépendent de quels autres. Ceux qui ont le moins de dépendances entrantes et sortantes sont les candidats les plus faciles pour l'extraction.

Étape 2 : Extrayez le module le plus douloureux en premier

Ne commencez pas par le module le plus facile ni le plus difficile. Commencez par celui qui cause le plus de douleur. Normalement, c'est celui qui remplit une ou plusieurs de ces conditions :

  • Il scale différemment du reste du système.
  • Il change très fréquemment et génère des conflits avec les autres équipes.
  • Il a des exigences technologiques différentes (besoin de GPU, d'une base de données différente, d'un runtime spécifique).
  • Ses pannes affectent de manière disproportionnée le reste du système.

Extrayez ce module comme un service indépendant. Déployez-le séparément. Donnez-lui son propre pipeline de CI/CD. Assignez-lui une équipe qui en est propriétaire.

Étape 3 : Placez un API gateway ou reverse proxy devant

C'est là que la magie du Strangler Fig opère. Vous placez un API gateway (Kong, NGINX, AWS API Gateway, Traefik) devant l'ensemble de votre système. Les requêtes qui allaient directement au monolithe passent maintenant par le gateway.

Le gateway décide : cette requête va au nouveau service, celle-ci continue d'aller au monolithe. Vous pouvez faire le changement graduellement — commencer en redirigeant 5 % du trafic vers le nouveau service, monitorer, et augmenter progressivement jusqu'à 100 %.

Si le nouveau service échoue, le gateway peut rediriger vers le monolithe. Migration sans risque existentiel.

Étape 4 : Migrez les données (la partie la plus difficile)

C'est là que la plupart des migrations se compliquent. Tant que votre nouveau service lit et écrit dans la même base de données que le monolithe, vous n'avez pas un vrai microservice — vous avez un monolithe distribué, ce qui est le pire des deux mondes.

L'objectif est que chaque service ait sa propre base de données. Le chemin pour y arriver :

  1. Dual writes. Le nouveau service écrit dans sa propre base de données ET dans celle du monolithe pendant une période de transition. Vous vérifiez que les données sont consistantes.
  2. Synchronisation par événements. Vous introduisez un système d'événements (Kafka, RabbitMQ, SNS/SQS) pour maintenir les données synchronisées entre le service et le monolithe.
  3. Cutover. Quand vous avez confiance que le nouveau service est la source de vérité, vous supprimez l'écriture dans la base de données du monolithe. Les autres modules qui ont besoin de ces données les obtiennent via l'API du nouveau service ou consomment ses événements.

Acceptez qu'il y aura une période d'eventual consistency. N'essayez pas de maintenir des transactions ACID entre les services — ce chemin mène aux distributed transactions et aux two-phase commits, qui sont fragiles et lents.

Étape 5 : Répétez

Extrayez le service suivant. Et le suivant. Chaque extraction devrait prendre des semaines, pas des mois. Si une extraction vous prend trois mois, vous extrayez probablement un module trop gros ou les dépendances n'étaient pas aussi claires que vous le pensiez.

Avec chaque extraction, le monolithe devient plus petit et plus gérable. À terme, ce qui reste est un service de plus — ou il se décompose naturellement en deux ou trois derniers services.

L'infrastructure dont vous avez besoin

Les microservices ne sont pas juste du code dans des dépôts séparés. Ils nécessitent une infrastructure de support qui n'existait pas dans votre monde monolithique :

Service discovery. Les services doivent pouvoir se trouver entre eux. Consul, DNS interne de Kubernetes, ou le service discovery de votre cloud provider.

Logging centralisé. Avec dix services, vous ne pouvez pas faire SSH sur chaque machine pour lire les logs. Vous avez besoin d'ELK Stack, Datadog, Grafana Loki — un endroit où tous les logs convergent.

Distributed tracing. Une requête utilisateur peut toucher cinq services. Sans tracing (Jaeger, Zipkin, Datadog APM), débugger une erreur revient à deviner dans quel service elle a pris naissance. Implémentez OpenTelemetry dès le premier service.

CI/CD par service. Chaque service a son propre pipeline. Vous modifiez le service de paiements, seul le service de paiements est déployé. GitHub Actions, GitLab CI ou l'outil que vous utilisez, mais indépendant par service.

Monitoring et alertes. Health checks, métriques de latence, taux d'erreur, saturation des ressources — par service. Si vous ne pouvez pas voir l'état de chaque service dans un dashboard, vous naviguez à l'aveugle.

Ce qu'il ne faut pas faire

N'extrayez pas tout en même temps. La tentation de "faisons la migration complète en un trimestre" est forte. Résistez. Chaque extraction est un risque contrôlé. Dix extractions simultanées, c'est le chaos.

Ne créez pas des nano-services. Un service qui n'a qu'un endpoint et 200 lignes de code ne devrait pas être un service. Il a tout l'overhead opérationnel d'un microservice sans aucun bénéfice. Un service doit représenter un domaine métier avec un sens propre.

Ne commencez pas par le module le plus couplé. Si le module utilisateurs est entremêlé avec tout le système, c'est le pire candidat pour la première extraction. Commencez par quelque chose avec moins de dépendances.

N'oubliez pas le monitoring. Chaque service que vous extrayez sans monitoring adéquat est une boîte noire en production. D'abord l'observabilité, ensuite l'extraction.

Structure des équipes : chaque service a besoin d'un propriétaire

Un microservice sans équipe responsable est un service orphelin. Et les services orphelins accumulent de la dette technique à une vitesse alarmante parce que personne ne se sent propriétaire de leur maintenance.

Chaque service a besoin d'une équipe qui en est clairement propriétaire. Cette équipe décide de sa roadmap, gère ses déploiements, répond quand il tombe à trois heures du matin. Elle n'a pas besoin d'être exclusivement dédiée à ce service — une équipe peut être propriétaire de deux ou trois services liés. Mais la propriété doit être explicite.

C'est exactement ce que décrit la loi de Conway inversée : vous concevez votre architecture pour qu'elle reflète la structure d'équipes que vous voulez avoir. Si vous voulez des équipes autonomes qui livrent de manière indépendante, vous avez besoin de services qui peuvent être développés et déployés de manière indépendante.

La migration est un processus, pas un projet

La migration du monolithe vers les microservices n'a pas de date de fin dans un diagramme de Gantt. C'est un processus continu qui évolue avec votre organisation. Vous extrayez des services quand la douleur le justifie, pas parce qu'un plan dit que c'est le moment.

Chez Conectia, nous travaillons avec des startups européennes qui sont exactement à ce stade : le monolithe leur a permis d'atteindre le product-market fit, et maintenant elles doivent faire évoluer l'architecture pour scaler. Les ingénieurs senior et architectes d'Amérique latine que nous fournissons ont guidé cette transition de nombreuses fois — ils savent quoi extraire en premier, comment éviter les erreurs classiques des migrations distribuées, et comment le faire sans arrêter les livraisons de fonctionnalités.

Parce que c'est la clé : votre business ne peut pas s'arrêter pendant que vous migrez. Les utilisateurs continuent d'utiliser le produit. L'équipe produit continue de demander des fonctionnalités. La migration doit se faire en parallèle de tout ça. Et ça nécessite des ingénieurs qui l'ont déjà fait.


Votre monolithe est en train de devenir un goulot d'étranglement ? Parlez à un CTO — nous vous connectons avec des architectes senior qui ont guidé cette transition sans arrêter les livraisons.

Prêt à construire votre équipe d'ingénierie ?

Parlez à un partenaire technique et déployez des développeurs validés par des CTOs en 72 heures.