Développement Web

Migrer un monolithe avec le pattern Strangler Fig (Spring)

13 دقائق للقراءة
📍 Vue d’ensemble : ce tutoriel s’inscrit dans une série sur l’architecture logicielle. Pour la carte conceptuelle, lisez le guide principal Architecture logicielle moderne : DDD, microservices et event-driven.

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/42 doit 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

Pour aller plus loin

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.

Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité