Sommaire

Migrer un monolithe PHP ancien vers une architecture modulaire peut se faire sans downtime ni régression critique. Voici la recette et les bonnes pratiques.

Diagnostic initial

Avant de migrer, il faut comprendre et cartographier : modules, dépendances, flux de données critiques, zones chaudes au niveau perf, zones mortes.

Outil indispensable : Deptrac pour visualiser les dépendances réelles entre namespaces.

Le piège du microservices big-bang

Voir un monolithe et penser "on va tout casser en 20 microservices" = burn-out assuré. La bonne question : où sont les frontières métier naturelles ?

Par exemple : 3 grands domaines (Catalogue, Commandes, Comptes), 2 services techniques (Notifications, Facturation). Objectif pragmatique : 5 services, pas 20.

Étape 1 : modulariser in-place

Avant d'extraire, on sépare. Chaque domaine devient un module isolé, avec ses propres entités, services et contrôleurs. Communication entre modules uniquement via interfaces publiques.

Deptrac enforce les règles : le module Orders peut dépendre de Catalog mais pas l'inverse. Build failure si violation. Discipline incontournable.

Étape 2 : strangler pattern

On choisit un sous-domaine (pas le cœur métier — le plus risqué à déplacer), comme les notifications.

On crée un service externe qui expose une API. On bascule les nouveaux appels du monolithe vers ce service via un feature flag. Jour J, on observe les logs. Jour J+7, tout est stable, on retire le vieux code du monolithe.

La clé : à chaque instant, le système fonctionne. Pas de big-bang, pas de nuit de déploiement, pas de "maintenance programmée".

Étape 3 : Outbox pattern

Quand le monolithe doit communiquer avec les services extraits, on utilise un outbox : chaque événement métier est écrit dans une table outbox_events dans la même transaction que le changement de données. Un worker asynchrone pousse ensuite vers Kafka/RabbitMQ.

Avantage : pas de "phantom read" entre DB et message bus. Pas de perte de message en cas de crash.

Étape 4 : extraction progressive

Le domaine des commandes est souvent le plus risqué (cœur métier, données sensibles) : il vaut mieux le garder pour la fin. À ce stade, le monolithe ne contient plus que l'essentiel et l'extraction devient plus simple, le reste ayant servi de rodage.

Ce qui a été gardé dans le monolithe

Quelques features peu critiques, utilisées par une petite partie des utilisateurs : administration interne, reporting annuel. Microserviser n'apportait rien. Règle : si ça marche et ça ne grandit pas, ne touchez pas.

Les pièges évités

  • Double-écriture : toujours outbox, jamais "écris dans les deux DB en parallèle".
  • Transaction distribuée : évitée systématiquement via saga pattern ou eventual consistency.
  • Ops explosion : 5 services = 5 déploiements, 5 monitoring, 5 alertes. Automatisez tout avant d'extraire le deuxième.
  • Latence : mesurez avant/après. Certains appels intra-module en 1ms deviennent 50ms en inter-service.

Les outils

  • Deptrac (dépendances).
  • Symfony Messenger ou Laravel queues avec Redis/RabbitMQ.
  • OpenTelemetry pour le tracing distribué (indispensable).
  • Grafana + Prometheus pour observer.

Vous avez un monolithe qui craque aux jointures ? Je fais ce type de mission.