Migrer un monolithe Laravel vers des microservices
Une approche pas à pas pour découper un monolithe Laravel sans faire tomber la production : quand ne pas le faire, les prérequis, le strangler fig pattern, trouver les frontières, et la partie dure, la donnée.
Migrer un monolithe vers des microservices est l'une des décisions les plus complexes qu'une équipe tech puisse prendre. Bien exécutée, elle débloque le scaling indépendant et l'autonomie des équipes. Mal exécutée, elle crée un monolithe distribué avec tout le coût opérationnel des microservices et aucun des bénéfices. Voici comment le faire de façon incrémentale, et comment savoir si vous devriez.
D'abord, décidez si vous devez migrer
La plupart des applications n'ont pas besoin de microservices. Les problèmes qu'on impute au monolithe, déploiements lents, code emmêlé, équipes qui se marchent dessus, se résolvent en général par un monolithe modulaire : des frontières de modules claires, des dépendances imposées, un seul déployable. Passez aux services quand une partie précise a besoin de scaler ou de se déployer indépendamment, ou quand des équipes ne peuvent réellement pas travailler sans se bloquer, pas parce que l'architecture est à la mode.
| Option | Déploiement | Données | Bon quand |
|---|---|---|---|
| Monolithe | Un seul | Une base | Petite équipe, produit jeune, domaine flou |
| Monolithe modulaire | Un seul | Une base, schémas par module | La plupart des équipes ; modules clairs, zéro surcoût ops |
| Microservices | Par service | Une base par service | Des parties scalent ou déploient indépendamment, plusieurs équipes |
Les prérequis avant d'extraire quoi que ce soit
- ✓Du tracing distribué et des logs centralisés ; impossible de déboguer une requête entre services sans ça.
- ✓Un CI/CD automatisé par service, pour qu'un nouveau service ne soit pas un déploiement manuel.
- ✓Une suite de tests en qui vous avez confiance sur le monolithe, pour savoir si une extraction a cassé quelque chose.
- ✓Une lecture claire de vos frontières de domaine ; si vous ne pouvez pas nommer vos bounded contexts, vous n'êtes pas prêt.
Le strangler fig pattern
Au lieu d'une réécriture big-bang, vous routez le trafic à travers une façade et redirigez progressivement des tranches du monolithe vers de nouveaux services. Le monolithe continue de tourner et reste le fallback. Avec le temps il rétrécit jusqu'à ce qu'il ne reste que ce qui est soit retiré, soit devenu lui-même juste un service de plus.
client
|
[ gateway / façade ]
/ \
monolithe nouveau service
(rétrécit) (grandit, un contexte à la fois)Ne tentez jamais une réécriture complète d'un monolithe en production. Extrayez un bounded context à la fois, gardez le monolithe comme fallback, et migrez le trafic progressivement derrière un feature flag pour pouvoir revenir en arrière en quelques secondes.
Trouver les frontières avec le DDD
La partie la plus difficile n'est pas le code, c'est de choisir où couper. Utilisez le Domain-Driven Design pour identifier les bounded contexts, et une session d'event storming avec les gens qui connaissent le domaine pour les faire émerger. Extrayez le contexte le moins couplé en premier : il vous apprend la mécanique au risque le plus bas.
- ✓Cartographiez le domaine : agrégats, bounded contexts, et les événements qui passent entre eux.
- ✓Repérez la douleur : quels modules déploient toujours ensemble, lesquels se ralentissent, sur lequel une équipe est bloquée.
- ✓Décidez la propriété des données : chaque service possède ses données, et aucun autre service ne lit ses tables directement.
- ✓Écrivez les contrats d'API et d'événements avant une ligne de code de service.
Étapes pratiques avec Laravel
Extrayez d'abord le module à l'intérieur du monolithe (son propre namespace, une interface à la frontière), placez une couche anti-corruption devant pour que le reste du monolithe parle à une interface, puis déplacez l'implémentation derrière HTTP ou des événements, et basculez avec un flag.
// Avant : couplage direct dans le monolithe
class OrderController extends Controller {
public function store(Request $request, UserService $users) {
$user = $users->find($request->user_id);
// ...
}
}
// Après : la frontière est une interface ; l'implémentation appelle le service
interface UserDirectory {
public function find(int $id): ?UserData;
}
class HttpUserDirectory implements UserDirectory {
public function find(int $id): ?UserData {
$data = Http::get(config('services.users.url') . "/users/{$id}")->json();
return $data ? UserData::fromArray($data) : null;
}
}La partie dure : la donnée
Découper le code est simple ; découper la base est là où les migrations calent. Faites-le par étapes : donnez d'abord à chaque contexte son propre schéma dans la base partagée et arrêtez les jointures inter-schémas, puis déplacez le nouveau service vers sa propre base. Pendant la transition, gardez les deux synchronisées avec une table outbox ou du change data capture plutôt qu'avec des doubles écritures depuis le code applicatif, difficiles à rendre fiables.
Communication : synchrone ou asynchrone
Utilisez HTTP ou gRPC synchrone uniquement quand l'appelant a besoin d'une réponse immédiate : une authentification, une autorisation de paiement. Utilisez des événements asynchrones (RabbitMQ, Kafka) pour tout ce qui peut arriver plus tard : notifications, logs d'audit, analytics, et mises à jour inter-services. Une chaîne d'appels synchrones où A attend B qui attend C compose latence et échec à chaque saut.
Le mode de défaillance : un monolithe distribué
Si vous ne pouvez pas déployer un service sans en redéployer d'autres, ou si un bug dans l'un fait tomber des fonctionnalités qui devraient être indépendantes, vous avez des microservices de nom et un monolithe en pratique, désormais avec des appels réseau au milieu. Les causes habituelles sont une base partagée et des chaînes d'appels synchrones. Les deux méritent qu'on s'arrête pour les corriger avant d'extraire le service suivant.
FAQ
- Quand migrer un monolithe Laravel vers des microservices ?
- Quand une partie précise du système a besoin de scaler ou de se déployer indépendamment, ou quand plusieurs équipes se bloquent réellement dans une seule codebase. Si la douleur est des déploiements lents ou du code emmêlé, un monolithe modulaire corrige ça sans le coût opérationnel. Ne migrez pas parce que le terme est à la mode.
- Qu'est-ce que le strangler fig pattern ?
- Une stratégie de migration où vous routez le trafic à travers une façade et redirigez progressivement des tranches du monolithe vers de nouveaux services, un bounded context à la fois. Le monolithe continue de tourner comme fallback et rétrécit avec le temps, donc il n'y a jamais de bascule big-bang.
- Comment séparer la base de données d'un monolithe ?
- Par étapes. Donnez d'abord à chaque bounded context son propre schéma dans la base partagée et arrêtez les jointures inter-schémas. Puis déplacez le nouveau service vers sa propre base, en gardant les deux synchronisées pendant la transition avec une table outbox ou du change data capture plutôt que des doubles écritures applicatives.
- Combien de temps prend une migration microservices ?
- Des mois, pas des semaines, et ça dépend du nombre de bounded contexts et de l'enchevêtrement des données. Extrayez un contexte, laissez-le tourner en production, consolidez, puis faites le suivant. Les équipes qui bâclent les premières extractions finissent en général avec un monolithe distribué.
- Faut-il tout migrer ?
- Non. Extrayez les parties qui ont besoin de scaling ou de déploiement indépendant et laissez le reste dans le monolithe. Un coeur monolithique stable entouré de quelques services est un état final courant et sain ; la décomposition complète en vaut rarement la peine.
Une migration microservices se mesure en mois. Avancez méthodiquement, mesurez le résultat de chaque extraction face à la raison qui vous a fait commencer, et n'hésitez pas à consolider avant la suivante.
Besoin d'aide sur ce sujet ? Migration Microservices
Découvrir ce service →