Microservices : le piège monolithique distribué
Diviser votre application en 10 services ne la rend pas plus rapide. Cela rend le débogage plus difficile. Un guide technique sur les monolithes modulaires, la conception pilotée par domaine (DDD) et le modèle Saga.
En 2015, le mantra de l’industrie était absolu : « Les monolithes sont des dinosaures. Les microservices sont l’avenir. » D’ici 2025, la gueule de bois s’est installée. Les entreprises qui divisent aveuglément leurs applications se noient désormais dans ce que nous appelons le Monolithe distribué : un système avec toute la complexité de l’informatique distribuée et aucun des avantages du découplage.
Chez Maison Code Paris, nous agissons comme la conscience architecturale de nos clients. Nous héritons souvent de projets « Microservices » où une simple connexion utilisateur touche six services différents, a une latence de 2 secondes et coûte 5 000 €/mois en frais d’entrée dans le cloud.
Cet article est un guide sobre et technique expliquant quand adopter les microservices et, plus important encore, quand les fuir.
Pourquoi Maison Code en parle
Chez Maison Code Paris, nous agissons comme la conscience architecturale de nos clients. Nous héritons souvent de stacks “modernes” construites sans compréhension fondamentale de l’échelle.
Nous abordons ce sujet car il représente un point de pivot critique dans la maturité de l’ingénierie. Une mise en œuvre correcte différencie un MVP fragile d’une plateforme résiliente de niveau entreprise.
Les arguments en faveur du monolithe modulaire
Avant de taper « mkdir service-user », considérez le Monolithe modulaire. Il s’agit d’une seule unité déployable (un binaire/conteneur) où le code est strictement séparé en modules (packages) qui reflètent les domaines métier.
La structure est importante
Un “Spaghetti Monolith” contient des fichiers qui s’importent les uns les autres de manière aléatoire. Un monolithe modulaire a des limites strictes imposées par des règles de peluchage ou des cibles de compilation.
src/
module/
auth/ # Contexte délimité : Authentification
api/#Interface publique
interne/# Implémentation privée (base de données, logique)
inventaire/ # Contexte délimité : Inventaire
API/
interne/
facturation/# Contexte délimité : facturation
API/
interne/
La loi de Déméter : Le code dans facturation ne peut pas importer inventaire/interne. Il ne peut appeler que « inventaire/api ».
Si vous suivez cette validation, vous obtenez 90% des avantages des Microservices (Séparation des équipes, code plus propre) avec 0% des inconvénients (Latence du réseau, cohérence éventuelle, complexité du déploiement).
Quand diviser (les conditions aux limites)
Nous conseillons uniquement de diviser un module en un microservice distinct s’il satisfait à l’un de ces critères stricts :
-
Exigences matérielles hétérogènes : * L’API principale est liée aux E/S. Il a besoin de 1 Go de RAM et de 0,5 CPU.
- Le Processeur d’image est lié au processeur. Il a besoin de 16 Go de RAM et d’un accès GPU.
- Décision : Extrayez le processeur d’image. Déployer des nœuds GPU pour l’API est un gaspillage d’argent.
-
Vitesse de sortie indépendante : * L’équipe Checkout envoie le code toutes les heures.
- La Ledger Team (Comptabilité) pousse une fois par mois après un audit rigoureux.
- Décision : Extrayez le paiement pour éviter de bloquer l’équipe à grande vitesse.
-
Isolement fatal (le « rayon de souffle ») :
- Si le PDF Generator présente une fuite de mémoire et plante, il ne devrait pas désactiver la Passerelle de paiement.
- Décision : Isoler les composants instables ou à risque.
Modèles de communication : REST, gRPC et événements
Une fois que vous disposez de plusieurs services, le problème le plus difficile est la communication. Les « appels de fonction » deviennent des « paquets réseau ».
Synchrone : le piège HTTP
Le service A appelle le service B via HTTP/REST.
GET /utilisateurs/123.
Cela couple la disponibilité de A à B. Si B est en panne, A échoue. Si B est lent, A se bloque. Si A appelle B, qui appelle C, qui appelle D, vous disposez d’une Chaîne d’appels. Si chaque service a une disponibilité de 99,9 %, une chaîne de 4 services a une disponibilité de 0,999^4 € \environ 99,6%€. Vous concevez un échec valide.
gRPC : la variante haute performance
Pour la communication interne, nous préférons gRPC (Protobufs) à REST/JSON.
- Binary Packed : charges utiles 10 fois plus petites.
- Fortement typé : la génération de code garantit que le service A sait exactement ce que le service B attend.
- Multiplexage : prise en charge HTTP/2 prête à l’emploi.
Asynchrone : l’architecture événementielle
C’est le Saint Graal du découplage. Le service A n’appelle pas le service B. Le service A émet un événement.
- Les utilisateurs s’enregistrent (Service A).
- Le service A publie l’événement « USER_REGISTERED » sur Kafka/RabbitMQ.
- Le service B (e-mail) consomme l’événement -> envoie un e-mail de bienvenue.
- Le service C (Analytics) consomme l’événement -> Tableau de bord des mises à jour.
Si le service B est hors ligne (maintenance, crash), le service A continue de fonctionner. Le message reste dans la file d’attente. Lorsque le service B revient en ligne, il traite le retard. Il s’agit de Résilience.
Cohérence des données : la partie la plus difficile
Dans un monolithe, vous avez des Transactions ACID.
COMMENCER LA TRANSACTION ; INSÉRER Utilisateur ; INSÉRER le compte ; ENGAGER ;
Soit les deux se produisent, soit aucun des deux n’arrive.
Dans Microservices, vous disposez d’une Base de données par service.
Le service A a users_db. Le service B a accounts_db. You cannot run a transaction across two different database servers.
Le modèle de la saga
Comment gérer une transaction distribuée ? Scénario : l’utilisateur passe une commande.
- Service d’inventaire : stock de réserve.
- Service de paiement : Carte de paiement.
- Service d’expédition : Imprimez l’étiquette.
Que se passe-t-il si la « Carte de recharge » échoue ? Vous avez déjà réservé du stock. Vous devez “Annuler” l’étape 1. Il s’agit d’une Transaction compensatoire.
diagramme de séquence
participant O en tant qu'orchestrateur d'ordre
participant I comme inventaire
participant P comme paiement
O->>I : Stock de réserve (article X)
I-->>O : Succès
O->>P : facturez 100 $
P -->>O : Échec (nsf)
O->>I : LIBÉRER le stock (compenser)
I-->>O : Succès
O -->>Utilisateur : Échec de la commande
Mettre en œuvre correctement Sagas est incroyablement complexe. Vous avez besoin d’un orchestrateur de machines d’état (comme les AWS Step Functions standard ou Temporal.io). Si vous n’avez pas besoin de cette complexité, ne créez pas de microservices.
Observabilité : voir dans le noir
Dans un monolithe, le débogage est tail -f /var/log/app.log.
Dans les microservices, une seule requête utilisateur peut toucher 10 conteneurs différents sur 10 nœuds différents.
Vous Devez mettre en œuvre le Traçage distribué (OpenTelemetry). Chaque requête obtient un « TraceID » au niveau de l’équilibreur de charge d’entrée. Cet identifiant est propagé dans les en-têtes HTTP (« X-B3-TraceId ») à chaque service en aval.
Des outils comme Jaeger, Datadog ou Honeycomb visualisent ensuite la « cascade » de la requête. * « Pourquoi ces 500 ms étaient-ils lents ? »
- “Oh, le service D a mis 450 ms sur une requête DB.”
Sans trace, vous volez à l’aveugle.
Infrastructure : exemple de code
Nous utilisons Terraform pour provisionner l’infrastructure des services indépendants.
# service-paiement.tf
ressource "kubernetes_deployment" "paiement" {
métadonnées {
nom = "service de paiement"
}
spécification {
répliques = 3
sélecteur {
match_labels = {
app = "paiement"
}
}
modèle {
métadonnées {
étiquettes = {
app = "paiement"
}
}
spécification {
conteneur {
image = "code maison/paiement:v1.2"
nom = "paiement"
# La règle d'or : limiter les ressources
ressources {
limites = {
processeur = "500 m"
mémoire = "512Mi"
}
requêtes = {
processeur = "250 m"
mémoire = "256Mi"
}
}
env {
nom = "DB_HOST"
valeur_from {
secret_key_ref {
nom = "paiement-db-creds"
clé = "hôte"
}
}
}
}
}
}
}
}
La stratégie de décomposition : la figue étrangleur
Si vous possédez un ancien monolithe, ne le réécrivez pas à partir de zéro. Utilisez le Modèle de figue étrangleur.
- Placez un proxy (API Gateway) devant le Monolith.
- Acheminez tout le trafic vers Monolith.
- Identifiez un contexte délimité (par exemple, « Avis »).
- Créez un « Service d’avis ».
- Changer de proxy : si le chemin est « /api/reviews », dirigez-vous vers le nouveau service. Sinon, route vers Monolith.
- Répétez jusqu’à ce que le monolithe disparaisse.
11. Le rôle de la passerelle API
Vous ne voulez pas que 10 services gèrent l’authentification de manière indépendante. Vous placez une passerelle (Kong, Tyk ou AWS API Gateway) en périphérie. Il gère :
- Auth : validation JWT.
- Limitation de débit : 100 req/min par IP.
- Mise en cache : met en cache les requêtes GET publiques.
- Routage :
/api/v1/users-> Service utilisateur. Cela maintient vos services internes « stupides » et concentrés sur la logique métier.
12. Service Mesh (Istio / Linkerd)
Lorsque vous disposez de 50 services, le « Service A » qui communique avec le « Service B » nécessite un cryptage (mTLS). La gestion manuelle des certificats est un enfer. Un Service Mesh injecte un “Sidecar Proxy” (Envoy) à côté de chaque conteneur. Le proxy gère :
- mTLS : chiffrement automatique.
- Nouvelles tentatives : “En cas d’échec, réessayez 3 fois avec une interruption exponentielle”.
- Circuit Breaking : “Si le service B présente 50 % d’erreurs, arrêtez de l’appeler pendant 1 minute”. Il dissocie la logique de réseau du code d’application.
13. Tests sous contrat (Pacte)
Comment tester le service A sans lancer le service B ?
Tests de contrat.
Le service A (Consommateur) écrit un “Fichier Pact” :
“Je m’attends à ce que GET /user renvoie { id: string }”.
Le service B (fournisseur) exécute un test sur ce fichier Pact dans son pipeline CI.
Si le service B remplace « id » par « userId », la construction échoue.
Cela détecte les modifications importantes avant le déploiement.
14. Conclusion
Les microservices sont un modèle de mise à l’échelle organisationnelle et non une optimisation des performances. Ils introduisent la Latence du réseau, les Problèmes de cohérence et la Complexité opérationnelle en échange de la Vitesse d’équipe et de l’Évolutivité indépendante.
Si vous êtes une équipe de 5 développeurs, vous construisez un Monolith. Vous pourriez l’appeler Microservices, mais vous construisez simplement une machine à douleur distribuée.
Faites évoluer votre structure de code avant de faire évoluer votre infrastructure.
**[Engagez nos Architectes](/contact)**.