Tout a commencé un lundi matin, quand notre API a mis 8 secondes à répondre. Huit secondes. Pour une requête qui en prenait normalement 200ms. C'est là qu'on a su qu'il était temps de changer quelque chose.
Je vais vous raconter notre parcours de migration du monolithe vers les microservices. Pas la version idéalisée des livres, mais la vraie — avec ses galères, ses surprises et ses victoires.
1. Pourquoi quitter le monolithe
Notre monolithe n'était pas un mauvais projet. C'était une API Node.js / Express bien structurée qui tournait depuis 2019. Il gérait l'authentification, les projets, la facturation, les notifications — tout dans un seul repo, un seul process, une seule base de données.
Mais à 50 000 requêtes par jour, les fissures ont commencé à apparaître :
- Déploiements à risque : un changement mineur dans le module de notifications pouvait planter toute l'API.
- Scalabilité impossible : impossible de scaler uniquement le module de search sans dupliquer toute l'app.
- Couplage invisible : après 2 ans, les dépendances entre modules étaient devenues un plat de spaghetti.
- Onboarding lent : un nouveau dev mettait 3 semaines à être productif.
Le déclic : On ne migre pas vers les microservices parce que c'est à la mode. On migre quand le monolithe devient un frein mesurable à la vélocité de l'équipe.
2. Notre stratégie de découpage
On a opté pour une approche progressive — le pattern Strangler Fig. L'idée : étrangler le monolithe petit à petit en extrayant des fonctionnalités une par une, plutôt que de tout réécrire d'un coup (ce qui est le meilleur moyen de se planter).
La stack cible
Pour chaque microservice, on a standardisé sur :
yaml# Architecture d'un microservice type service: name: user-service runtime: Node.js 20 + Fastify database: PostgreSQL (par service — pas de DB partagée !) communication: sync: gRPC (pas de REST entre services !) async: RabbitMQ (pour les événements) monitoring: Prometheus + Grafana tracing: OpenTelemetry → Jaeger deployment: Kubernetes + Helm
On a commencé par le module le moins critique et le plus autonome : le service d'authentification. Un bon candidat parce qu'il a des frontières claires, peu de dépendances, et un impact mesurable.
typescript// Exemple : définition gRPC du service auth syntax = "proto3"; service AuthService { rpc ValidateToken(ValidateTokenRequest) returns (ValidateTokenResponse); rpc RefreshToken(RefreshTokenRequest) returns (RefreshTokenResponse); } message ValidateTokenRequest { string token = 1; } message ValidateTokenResponse { string user_id = 1; repeated string roles = 2; int64 expires_at = 3; }
3. Les pièges qu'on n'a pas vus venir
Piège n°1 : La latence réseau
Dans le monolithe, un appel entre modules c'était une fonction — ~0.1ms. En microservices, c'est un appel réseau — ~2-5ms en local, 20-50ms en production. Multiplié par 10 appels en cascade, vous passez de 1ms à 500ms sans vous en rendre compte.
Notre solution : on a implémenté un cache distribué (Redis) et du circuit breaking (avec opossum) dès le début. On a aussi banni les appels en chaîne — un service ne doit jamais appeler un autre service qui en appelle un troisième.
Piège n°2 : La cohérence des données
Quand chaque service a sa propre base de données, fini les transactions ACID qui traversent les modules. On a dû apprendre la cohérence éventuelle, les sagas, et le pattern outbox. C'est un shift mental énorme.
« La cohérence éventuelle, c'est facile à comprendre sur le papier. C'est dans la vraie vie, quand un utilisateur crée un compte et que son email de bienvenue arrive 30 secondes plus tard, que ça devient... intéressant. »
— Yacine, après 3 nuits blanches
Piège n°3 : Le monitoring devient vital
Avec 7 microservices, vous avez 7 logs différents, 7 endroits où chercher une erreur, et 49 interactions possibles. Sans tracing distribué (on utilise OpenTelemetry + Jaeger), vous êtes aveugle. On a appris ça à nos dépens le jour où un bug a mis 4 heures à être localisé.
4. Les résultats après 6 mois
Est-ce que ça valait le coup ? Voici les chiffres, honnêtement :
- Temps de déploiement : de 45 minutes à 8 minutes (on déploie uniquement le service modifié)
- P99 de latence : de 3200ms à 480ms (grâce au scaling indépendant)
- Fréquence de déploiement : de 2x/semaine à 15x/jour
- Temps d'onboarding : de 3 semaines à 4 jours (un nouveau dev peut commencer sur un microservice simple)
- Coût infra : +25% (oui, les microservices coûtent plus cher en ressources — soyez-en conscients)
Le coût a augmenté, c'est vrai. Mais la vélocité de l'équipe a été multipliée par 4, et le nombre d'incidents en production a baissé de 60%. Le ROI est clairement positif pour nous.
5. Ce qu'on aurait aimé savoir avant
- Ne pas commencer par les microservices. Commencez par un monolithe bien modulaire. Les microservices sont une solution à un problème de scalabilité organisationnelle, pas technique.
- Investissez dans le tracing avant la migration. Installez OpenTelemetry sur votre monolithe d'abord. Vous aurez besoin de cette visibilité.
- Automatisez tout. CI/CD, tests d'intégration, déploiement, rollback. Sans automatisation, 7 services deviennent ingérables.
- Prévoyez le coût. Les microservices coûtent plus cher en infrastructure, en monitoring, et en complexité cognitive. Budgetez-le.
- La communication asynchrone est votre amie. Utilisez des événements plutôt que des appels synchrones dès que c'est possible.
Le vrai secret : La migration vers les microservices n'est pas un projet technique — c'est un projet organisationnel. Si votre équipe n'est pas prête à fonctionner en mode décentralisé (chaque service a son owner, ses propres décisions tech), restez sur du monolithe modulaire. Vous dormirez mieux.