Réécrire un monolithe d’un seul coup pour le transformer en microservices est l’un des projets les plus risqués qu’une équipe puisse entreprendre. Pendant des mois, on développe en parallèle de l’existant, sans rien livrer, en pariant que le nouveau système rattrapera un jour toutes les fonctionnalités de l’ancien — un pari souvent perdu. Le pattern Strangler Fig, décrit par Martin Fowler en 2004, propose l’inverse : faire grandir le nouveau système autour de l’ancien, fonctionnalité par fonctionnalité, jusqu’à ce que le monolithe s’éteigne de lui-même. Dans ce tutoriel, vous allez extraire une première tranche d’une plateforme de commandes vers un service Spring Boot, sans jamais interrompre le service.
La décision même de découper — quand c’est justifié et selon quels critères — mérite sa propre réflexion, développée dans Architecture microservices : quand et comment découper son application. Ici, on suppose la décision prise et l’on se concentre sur le comment migrer sans casse.
🎯 Ce que vous allez apprendre
- Comprendre la mécanique du Strangler Fig et pourquoi elle réduit le risque.
- Choisir la première tranche à extraire selon des critères concrets.
- Intercaler une façade de routage devant le monolithe avec Spring Cloud Gateway.
- Construire un nouveau service Spring Boot et y rediriger une partie du trafic.
- Protéger le nouveau service par une couche anti-corruption et gérer la transition des données.
- Basculer progressivement, puis retirer le code mort de l’ancien système.
🛠️ Ce que vous allez construire
Une passerelle qui reçoit tout le trafic d’une application existante et le route, par défaut, vers le monolithe. Vous y brancherez ensuite un nouveau service « Catalogue » : les requêtes /api/products/** partiront vers ce service neuf, tout le reste continuant vers l’ancien. Au fil de la migration, vous déplacerez d’autres tranches jusqu’à ce que le monolithe n’ait plus rien à servir.
Prérequis
- Java 21 (ou plus) et Maven ou Gradle.
- Spring Boot 4 et Spring Cloud Gateway 4 (versions courantes au moment d’écrire ; Spring Boot 4.0 est paru en novembre 2025).
- Un monolithe existant qui écoute en HTTP (ici, supposé sur le port 8080).
- Niveau intermédiaire. Test express : si vous savez démarrer une application Spring Boot et écrire un contrôleur REST, vous êtes prêt.
- ⏱️ Temps estimé : ~60 minutes.
Étape 1 — Le principe du figuier étrangleur
Le nom vient d’une plante tropicale qui pousse sur un arbre hôte, l’enserre peu à peu, et finit par le remplacer entièrement une fois l’hôte mort. Appliqué au logiciel, le principe est limpide : on n’attaque pas le monolithe de front, on l’enveloppe. Une couche d’interception se place devant lui et capte tout le trafic. Au départ, elle se contente de tout transmettre à l’ancien système — rien ne change pour l’utilisateur. Puis, tranche par tranche, on redirige certaines requêtes vers de nouveaux services. À chaque étape, le monolithe rétrécit, le nouveau système grandit, et l’application reste en ligne sans interruption.
L’intérêt par rapport à la réécriture totale est qu’on livre de la valeur en continu et qu’on peut s’arrêter à tout moment. Si le budget se tarit après trois tranches migrées, on dispose d’un système hybride parfaitement fonctionnel, pas d’un chantier abandonné. Chaque tranche est aussi réversible : tant qu’on n’a pas supprimé l’ancien code, un simple changement de route ramène le trafic vers le monolithe en cas de problème.
✅ Point d’étape — Assurez-vous d’avoir en tête les trois temps de chaque tranche : intercepter (tout passe par la façade), dévier (une route part vers le neuf), retirer (l’ancien code de cette tranche est supprimé). Tant qu’une tranche n’a pas franchi les trois, elle n’est pas terminée.
Étape 2 — Choisir la première tranche
Le choix de la première fonctionnalité à extraire conditionne le moral de l’équipe et la confiance dans la démarche. On évite la pièce maîtresse, trop risquée pour un coup d’essai. On cherche une tranche qui combine trois qualités : un couplage faible avec le reste (peu de dépendances croisées), une frontière nette — idéalement un contexte borné identifié par le Domain-Driven Design — et une valeur réelle à l’extraire (une partie qui doit monter en charge seule, ou évoluer souvent).
Dans notre boutique, le catalogue de produits coche ces cases. Il est surtout consulté en lecture, ses données changent peu, et il subit des pics quand une promotion attire du trafic — un bon candidat à une montée en charge indépendante. Surtout, sa frontière est claire : afficher des produits ne dépend pas de la logique de commande. On commence donc par lui.
Étape 3 — Intercaler la façade de routage
La première brique technique est la passerelle. Avec Spring Cloud Gateway, on crée un projet Spring Boot minimal dont le seul rôle est de router. Au démarrage, une seule route attrape tout (/**) et l’envoie au monolithe : la façade est transparente. Notez la structure de configuration, qui place les routes sous server.webflux, conformément à la version courante de Spring Cloud Gateway.
# application.yml de la passerelle (port 8000)
server:
port: 8000
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: legacy-monolith
uri: http://localhost:8080
predicates:
- Path=/**
Vous démarrez la passerelle, vous faites pointer le nom de domaine (ou le répartiteur de charge) vers le port 8000 au lieu du 8080, et… rien ne change pour l’utilisateur. C’est précisément le but : à ce stade, la façade est un simple relais. Mais elle est maintenant le point de passage obligé, prêt à dévier le trafic tranche par tranche. Pour les filtres avancés (authentification, limitation de débit), le tutoriel dédié à Spring Cloud Gateway approfondit cette passerelle.
✅ Point d’étape — Avec la passerelle démarrée,
curl http://localhost:8000/api/products/42doit renvoyer exactement la même réponse que le monolithe direct sur le port 8080. Si vous obtenez une erreur 503, vérifiez que le monolithe écoute bien et que l’URI de la route est correcte.
Étape 4 — Construire le nouveau service Catalogue
On crée maintenant un service Spring Boot indépendant pour le catalogue, qui écoutera sur le port 8081. Il expose la même interface publique que ce que servait le monolithe pour les produits, afin que la bascule soit invisible côté client.
// ProductController.java — service Catalogue, port 8081
package shop.catalog;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductRepository repository;
public ProductController(ProductRepository repository) {
this.repository = repository;
}
@GetMapping("/{id}")
public ProductView getById(@PathVariable String id) {
return repository.findById(id)
.map(ProductView::from)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "Produit introuvable"));
}
}
Le service possède sa propre base de données — règle absolue des microservices, rappelée dans le guide principal : on ne partage jamais une base entre deux services. La classe ProductView est un objet de présentation distinct de l’entité de persistance, pour que le format de l’API publique ne soit pas dicté par le schéma interne. Ce détail prépare l’étape suivante, où l’on devra parfois traduire le modèle hérité du monolithe.
Étape 5 — Dévier le trafic du catalogue
Le nouveau service est prêt. On ajoute alors une route plus spécifique dans la passerelle pour les produits, placée avant la route fourre-tout. Spring Cloud Gateway évalue les routes dans l’ordre, donc la plus précise doit venir en premier.
# application.yml de la passerelle, mis à jour
spring:
cloud:
gateway:
server:
webflux:
routes:
- id: catalog-service # nouvelle route, en premier
uri: http://localhost:8081
predicates:
- Path=/api/products/**
- id: legacy-monolith # route par défaut, en dernier
uri: http://localhost:8080
predicates:
- Path=/**
Désormais, toute requête vers /api/products/** part vers le service neuf, tandis que le reste continue vers le monolithe. La tranche « catalogue » est officiellement déviée. L’utilisateur ne voit aucune différence, mais sous le capot, deux systèmes coexistent et se partagent le travail. C’est l’essence du Strangler Fig : une migration qui avance par déplacements de frontières, jamais par grand soir.
Étape 6 — Couche anti-corruption et données
Un danger guette : que le nouveau service hérite des concepts brouillons du monolithe, par commodité. Pour l’éviter, le DDD propose la couche anti-corruption : un adaptateur qui traduit le modèle de l’ancien système vers le modèle propre du nouveau, et bloque toute contamination. Si le service Catalogue doit, pendant la transition, lire encore des données depuis le monolithe, il le fait à travers cette couche de traduction.
// LegacyProductGateway.java — couche anti-corruption
package shop.catalog.acl;
import shop.catalog.Product;
public class LegacyProductTranslator {
// Traduit la structure héritée (champs cryptiques) vers le modèle propre
public Product toDomain(LegacyProductRecord legacy) {
return new Product(
legacy.getPrdId(), // "prd_id" du monolithe
legacy.getLabel().trim(), // nettoyage des données héritées
PriceCents.of(legacy.getPriceCt()) // type-valeur propre au nouveau modèle
);
}
}
La question des données est le vrai nerf de la guerre. Trois stratégies coexistent selon le contexte : recopier les données une fois si elles sont stables (cas du catalogue), ou synchroniser en continu via des événements quand les deux systèmes les modifient pendant la transition. Cette synchronisation par messages est exactement le sujet du tutoriel sur l’architecture orientée événements : le monolithe publie « produit modifié », le nouveau service s’y abonne. On évite ainsi le piège mortel de la base de données partagée, qui rendrait les deux systèmes inséparables.
Étape 7 — Basculer en douceur, puis retirer l’ancien
Avant de déclarer la tranche terminée, on observe. Une bascule prudente surveille les taux d’erreur et les temps de réponse du nouveau service en conditions réelles. Si quelque chose dérape, on inverse la route en une ligne pour renvoyer le trafic vers le monolithe — filet de sécurité que seul le Strangler Fig offre. Une fois la confiance acquise sur plusieurs jours, on franchit la dernière étape, trop souvent négligée : supprimer le code correspondant dans le monolithe.
Cette suppression n’est pas cosmétique. Tant que l’ancien code des produits reste en place, deux implémentations de la même fonctionnalité existent, et la confusion s’installe : où corriger un bug ? Laquelle fait foi ? Retirer le code mort referme la tranche et rétrécit réellement le monolithe. C’est ce qui distingue une vraie migration d’une simple juxtaposition. On répète ensuite le cycle complet — intercepter, dévier, retirer — pour la tranche suivante.
✅ Point d’étape — Une tranche est terminée quand trois conditions sont réunies : le trafic part vers le nouveau service, les données sont cohérentes, et le code hérité a été supprimé du monolithe. Si l’une manque, la dette technique reste ouverte.
🐞 Pièges fréquents
| Symptôme | Cause probable | Correctif |
|---|---|---|
| La route fourre-tout capte les produits | Ordre des routes inversé | Placer la route spécifique /api/products/** avant /** |
| Les deux systèmes se marchent dessus sur les données | Base de données partagée | Chaque service a sa base ; synchroniser par événements ou recopie |
| Le nouveau service hérite des défauts de l’ancien | Absence de couche anti-corruption | Traduire explicitement le modèle hérité vers un modèle propre |
| Migration qui ne finit jamais | On dévie sans jamais supprimer l’ancien code | Clore chaque tranche par la suppression dans le monolithe |
| Erreur 503 à la passerelle | Service cible arrêté ou mauvaise URI | Vérifier que le service écoute et que le port de l’URI est correct |
✅ Récapitulatif
Vous avez migré une fonctionnalité d’un monolithe vers un service indépendant sans jamais arrêter l’application. La méthode tient en un cycle répétable : intercepter tout le trafic derrière une passerelle, dévier une tranche bien choisie vers un nouveau service, isoler ce service par une couche anti-corruption et des données propres, surveiller la bascule, puis supprimer le code hérité. En enchaînant ces cycles, on transforme un système monolithique en un ensemble de services sans le « big bang » risqué de la réécriture totale.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
| Façade de routage | Point de passage unique devant le monolithe (Spring Cloud Gateway) |
Route fourre-tout /** |
Renvoie par défaut vers le monolithe ; toujours en dernier |
| Route spécifique | Dévie une tranche vers un nouveau service ; placée en premier |
| Couche anti-corruption | Traduit le modèle hérité, empêche la contamination |
| Suppression du code hérité | Clôt la tranche et rétrécit réellement le monolithe |
💪 À vous de jouer
Ajoutez une seconde tranche : extrayez la consultation des avis clients (/api/reviews/**) vers un nouveau service sur le port 8082. Écrivez la route de passerelle correspondante et placez-la au bon endroit dans la liste.
Voir une solution
routes:
- id: catalog-service
uri: http://localhost:8081
predicates:
- Path=/api/products/**
- id: reviews-service # nouvelle tranche
uri: http://localhost:8082
predicates:
- Path=/api/reviews/**
- id: legacy-monolith # toujours en dernier
uri: http://localhost:8080
predicates:
- Path=/**
L’ordre importe : tant que reviews-service est avant legacy-monolith, ses requêtes ne tomberont jamais dans la route fourre-tout.
Tutoriels liés
- Architecture microservices : quand et comment découper — décider quelles tranches extraire et à quel moment.
- Architecture orientée événements avec RabbitMQ — synchroniser les données entre ancien et nouveau système.
Pour aller plus loin
- 🔝 Revenir à la vue d’ensemble : Architecture logicielle moderne.
- Martin Fowler, StranglerFigApplication — la description originale du pattern.
- Documentation Spring Cloud Gateway.
Questions fréquentes
Combien de temps dure une migration Strangler Fig ?
Cela dépend du nombre de tranches et du rythme de l’équipe, mais l’intérêt du pattern est précisément qu’il n’y a pas de date couperet : chaque tranche livrée est un gain acquis. Certaines migrations s’étalent sur plus d’un an, et c’est acceptable tant que chaque cycle se termine proprement.
Peut-on utiliser autre chose que Spring Cloud Gateway pour la façade ?
Oui. N’importe quel proxy inverse capable de router selon le chemin convient — un serveur web en frontal, par exemple. Spring Cloud Gateway est pratique dans un écosystème Java car il offre filtres, sécurité et observabilité dans le même outil.
Faut-il faire du DDD pour réussir un Strangler Fig ?
Ce n’est pas obligatoire, mais les frontières de contextes bornés sont les lignes de découpe les plus sûres. Tenter d’extraire une tranche dont la frontière est floue mène presque toujours à un service qui reste accroché au monolithe par mille fils.
Mots-clés : strangler fig, migration monolithe, microservices, Spring Boot, Spring Cloud Gateway, couche anti-corruption, refactoring incrémental.