Zum Inhalt springen

Strangler-Fig-Pattern: Monolith Schritt für Schritt migrieren, mit Diagramm

So ersetzt du einen Monolithen mit dem Strangler-Fig-Pattern: Routing-Fassade, vier Phasen, was zuerst herausgelöst wird, Daten ohne Dual Writes und Paritätsnachweis.

· Gründer & Entwickler

Veröffentlicht am

Kunden rufen ein Facade-API-Gateway auf. Es leitet /catalog/* (100 Prozent) an einen Catalog-Service mit eigener Datenbank, 25 Prozent von GET /orders/* an einen Orders-Service, den Rest an einen Legacy-Monolithen, dessen Datenbank per gestricheltem CDC-Pfad die Orders-Datenbank speist.

Dein Monolith ist zehn Jahre alt, jedes Release braucht einen langen Regressionszyklus, und jemand schlägt einen Rewrite vor: das neue System parallel bauen, Feature-Parität erreichen, an einem Wochenende umschalten. Währenddessen verändert sich das alte System weiter, und der Umschalttermin rutscht. Martin Fowler schreibt, er und seine Kollegen hätten erlebt, dass dieser Plan „meistens in Flammen aufgeht“.

Das Strangler-Fig-Pattern ersetzt das System stattdessen Stück für Stück. Eine Fassade vor dem Monolithen leitet jede Anfrage an den alten Code oder an einen neuen Service, und Fähigkeiten wandern einzeln, bis nichts mehr zum Monolithen geroutet wird. Das Routing ist der leichte Teil. Datenhoheit, Paritätsnachweis und der Abschluss sind die schweren.

Dieser Beitrag behandelt alle drei, mit einem Diagramm einer laufenden Migration.

Woher der Name kommt

2001 sah Fowler im Regenwald von Queensland Würgefeigen: Lianen, die auf einem Wirtsbaum keimen, im Boden wurzeln und den Baum überleben können, sodass die Feige in seiner Form zurückbleibt. Im Juni 2004 nutzte er sie als Metapher für das schrittweise Ersetzen eines Altsystems. Im April 2019 benannte er den Beitrag von „Strangler Application“ in „Strangler Fig Application“ um, um auf die Pflanze zu verweisen, und im August 2024 schrieb er ihn neu. Die aktuelle Version betont, wogegen sich Teams sträuben: Übergangsarchitektur, also Code, den du nur baust, damit Alt und Neu nebeneinander bestehen können.

Die Fassade übernimmt das Routing

Die Fassade ist ein Reverse Proxy oder API-Gateway vor dem Monolithen: NGINX, Envoy, ein Cloud-API-Gateway oder ein Load Balancer mit Pfadregeln. Sie schickt zunächst alles an den Monolithen. Danach ist jeder Migrationsschritt eine Routing-Änderung. Geroutet wird nach:

  • Pfad: /catalog/* geht an den Catalog-Service.
  • Methode: GET /orders/* vor POST /orders, damit zuerst die Lesezugriffe wandern.
  • Anteil: Ein Hash aus Session- oder User-ID hält jeden Nutzer auf einer Seite.
  • Header oder Mandant: Interne Nutzer oder ein Pilotkunde kommen zuerst.
# upstream blocks catalog, orders and monolith defined elsewhere
split_clients "${cookie_session}" $orders_read {
    25%      orders;       # canary share for reads
    *        monolith;
}
map $request_method $orders_target {
    GET      $orders_read;
    default  monolith;     # order writes stay in the monolith
}
server {
    location /catalog/ { proxy_pass http://catalog; }
    location /orders/  { proxy_pass http://$orders_target; }
    location /         { proxy_pass http://monolith; }
}

Ein Rollback ist eine einzige Änderung: Setze die 25-%-Zeile auf monolith (nginx lehnt einen 0-%-Eintrag ab). Hashe einen Schlüssel, den jede Anfrage mitbringt: Anfragen ohne das Cookie landen alle im selben Bucket.

Halte die Fassade schlank: Routing, TLS, Authentifizierung und Logging, keine Geschäftsregeln. Sie sitzt auf jeder Anfrage, also betreibe mehrere Instanzen und miss die zusätzliche Latenz. Das API-Gateway-Architekturdiagramm zeigt, was in ein Gateway gehört.

Die vier Phasen einer Migration

Phase Die Fassade routet Daten Fertig, wenn
0. Zuerst die Fassade Alles zum Monolithen Unverändert Fassade live, zusätzliche Latenz gemessen
1. Erste Fähigkeit Eine Route, etwa /catalog/*, zu einem neuen Service Der Service liest Monolith-Tabellen oder eine CDC-Kopie, danach eigene Route über einen vollen Geschäftszyklus bei 100 %
2. Meiste Last auf neuen Services Die meisten Routen; der Monolith behält eine bekannte Liste Jeder Service besitzt seine Tabellen Der Rest passt auf eine Seite
3. Monolith abgelöst Nichts mehr zum Monolithen Legacy-Datenbank archiviert, dann gelöscht Monolith abgeschaltet

Die Strangler-Fig-Seite von Microsoft nennt einen letzten Schritt: die Fassade entfernen oder als Adapter für Legacy-Clients behalten.

Auswählen, was zuerst herausgelöst wird

Gute erste Kandidaten haben drei Eigenschaften:

  • Eine klare Naht, nach Michael Feathers ein Ort, an dem du das Verhalten ändern kannst, ohne an dieser Stelle zu editieren: ein URL-Präfix, eine Queue, eine Modulgrenze.
  • Eine hohe Änderungsrate, damit sich der Umzug bald auszahlt. Die Versionsverwaltung zeigt sie.
  • Geringe Kopplung: wenige Joins in andere Tabellen, wenige Aufrufe zurück in den Monolithen.
git log --since="12 months ago" --format= --name-only -- src/ \
  | sort | uniq -c | sort -rn | head -20

Eine Rangliste für einen Online-Shop:

Kandidat Naht Änderungsrate Kopplung Urteil
Katalog-Browsing /catalog/* Hoch Niedrig Zuerst
Bestellhistorie GET /orders/* Mittel Mittel Als Zweites, erst Lesezugriffe
Checkout Ruft Preise, Lager und Zahlungen auf Hoch Hoch Nach seinen Abhängigkeiten
Nächtliche Reports SQL über das ganze Schema Niedrig Sehr hoch Zuletzt, oder ein Warehouse-Feed

Löse nach Geschäftsfähigkeit heraus, nicht nach technischer Schicht. Ein gemeinsamer „Datenzugriffs-Service“ bewegt nichts und koppelt alles.

Die Daten umziehen

Das Routing bewegt Anfragen, nicht Daten, und an den Daten stocken Migrationen. Entscheide in jedem Schritt, wem welche Tabelle gehört.

Strategie Funktionsweise Geeignet für Risiko
Geteilte Datenbank Der neue Service nutzt die Tabellen des Monolithen Die ersten Wochen Schemaänderungen im Monolithen brechen ihn
Change Data Capture Ein Connector streamt Zeilenänderungen aus dem Log des Monolithen Lesezugriffe zuerst verlagern Verzögerung, Legacy-Schemas, Einbahnstraße
Datenbank pro Service Der Service besitzt seine Tabellen; andere rufen seine API auf Der Endzustand Joins über Services werden zu API-Aufrufen
Dual Writes aus dem Code Die Anwendung schreibt in beide Datenbanken Nichts Ein Absturz zwischen den Schreibvorgängen lässt sie auseinanderlaufen

Die Hinweise von Microsoft nennen die übliche Reihenfolge: Der neue Service startet auf den Tabellen des Monolithen, bekommt dann eine eigene Datenbank, die einmal geladen und per CDC aktuell gehalten wird, und wird nach der Validierung zum System of Record. Solange die Legacy-Tabellen existieren, ist ein Rollback eine Routenänderung; danach ein Restore.

Vermeide Dual Writes: Ein Absturz zwischen zwei getrennten Schreibvorgängen lässt die Datenbanken uneins zurück, und gleichzeitige Updates können in unterschiedlicher Reihenfolge ankommen. Mach eine einzige Transaktion zur Quelle jeder Änderung, per CDC oder über ein Transactional Outbox. Das Change-Data-Capture-Diagramm behandelt Debezium und Replication Slots.

Die Anti-Corruption-Layer

Das Modell des Monolithen sickert durch: einstellige Statuscodes, Rechnungs- und Versandfelder in einer Tabelle, Regeln in Stored Procedures. Ein Service, der diese Zeilen konsumiert, erbt das Modell, das du verlassen willst. Eine Anti-Corruption-Layer, ein Pattern aus Eric Evans' Domain-Driven Design (2003), übersetzt an der Grenze:

  • Legacy zu neu: CDC-Events mit Legacy-Zeilen werden zu Bestellungen im neuen Modell.
  • Neu zu Legacy: Der Orders-Service liest Kundendaten über einen Adapter aus dem Monolithen.
  • Monolith zu neu: Sobald der Katalog umgezogen ist, ruft Monolith-Code die Catalog-API statt seiner alten Tabellen auf.

Halte Geschäftsregeln aus der Layer heraus. In einem hexagonalen Service liegt sie in den Adaptern, sodass der Kern nie Legacy-Typen sieht.

Parität nachweisen, bevor eine Route wandert

Jede Route durchläuft dieselben Schritte:

  1. Shadow. Die Fassade kopiert Anfragen an den neuen Service und verwirft dessen Antworten, wie es das Mirror-Modul von NGINX und das Request Mirroring von Envoy tun. Spiegle nur Lesezugriffe: Ein gespiegeltes POST /orders erzeugt eine zweite Bestellung.
  2. Vergleichen. Vergleiche beide Antworten, nachdem du Zeitstempel, IDs und Reihenfolge normalisiert hast. Diffy von Twitter, 2015 als Open Source veröffentlicht, betreibt zwei Kopien des alten Codes und ignoriert Unterschiede zwischen ihnen als Rauschen. Innerhalb des Monolithen führt die Scientist-Bibliothek von GitHub (Ruby, mit Ports in andere Sprachen) alten und neuen Pfad nebeneinander aus und gibt das alte Ergebnis zurück.
  3. Canary. Verlagere 1 %, 5 %, 25 %, 50 %, dann 100 % des echten Traffics, vergleiche Fehlerrate und p99-Latenz mit denen des Monolithen für dieselbe Route und halte jeden Schritt über eine tägliche Lastspitze. Das Deployment-Strategien-Diagramm behandelt die Mechanik.
  4. Halten. Halte bei 100 % den alten Pfad deploybar und seine Daten aktuell, auch über Ereignisse wie einen Monatsabschluss hinweg.
  5. Löschen: den alten Code, seine Tabellen und die Route.

Eine Route zurückzuschalten funktioniert nur, solange der Monolith aktuelle Daten hat. Sobald der neue Service die Schreibzugriffe besitzt, speise sie per Reverse CDC oder Events zurück, sonst gehen sie bei einem Rollback verloren.

Woran Strangler-Fig-Migrationen scheitern

  • Nie fertig werden. Die leichten 80 % wandern; Reports, Batch-Jobs und Admin-Oberflächen bleiben, und du betreibst zwei Systeme. Zähle in jedem Sprint, was übrig ist, und setze ein Abschaltdatum.
  • Ein verteilter Monolith. Services, die Tabellen teilen, gemeinsam deployt werden oder sich in langen synchronen Ketten aufrufen. Der Test ist ein Writer pro Tabelle.
  • Die Fassade als Flaschenhals: Geschäftslogik im Gateway oder eine einzige Instanz, die den gesamten Traffic trägt.
  • Versteckte Konsumenten. Reports und andere Teams lesen die Datenbank des Monolithen direkt, unsichtbar für die Fassade. Prüfe die Verbindungslogs, bevor du eine Tabelle verlagerst.
  • Feature-Parität als Ziel. Fowler merkt an, dass viel Legacy-Verhalten „eigentlich gar nicht gewollt ist“. Patterns of Legacy Displacement behandelt das und weitere Fallen.

Das Diagramm der Strangler-Fig-Migration

Kunden rufen ein Facade-API-Gateway auf. Es leitet /catalog/* (100 Prozent) an einen Catalog-Service mit eigener Datenbank, 25 Prozent von GET /orders/* an einen Orders-Service, den Rest an einen Legacy-Monolithen, dessen Datenbank per gestricheltem CDC-Pfad die Orders-Datenbank speist.

17 Komponenten · 10 Verbindungen

In Archboard öffnen
Diagramminhalt
  • Customers
  • Facade (API gateway)
  • Catalog service
  • Orders service
  • Legacy monolith
  • Catalog DB
  • Orders DB
  • Monolith DB
  • Debezium + Kafka
  • Anti-corruption layer
  • HTTPS
  • /catalog/* · 100%
  • GET /orders/* · 25%
  • everything else + 75% of GET /orders
  • reads, writes
  • reads
  • WAL changes
  • order row events
  • upserts in the new model
Eine laufende Monolith-Migration. Die Fassade schickt den ganzen Katalog-Traffic an einen neuen Service mit eigenen Daten, ein Viertel der Bestell-Lesezugriffe an einen per Change Data Capture gespeisten Orders-Service, den Rest an den Monolithen. Jeder Schritt ist umkehrbar.

So liest du es:

  • Von links nach rechts: Kunden rufen die Fassade auf, die den Traffic nach Route aufteilt; die Datenbanken liegen rechts.
  • Grün, umgezogen: Das gesamte /catalog/* geht an den Catalog-Service, der seine Daten besitzt.
  • Bernstein, Canary: 25 % von GET /orders/* gehen an den Orders-Service. Schreibzugriffe und die übrigen Lesezugriffe gehen weiter zum Monolithen.
  • Grau, Legacy: Der Monolith bedient den Rest und besitzt weiterhin die Bestelldaten.
  • Violett, gestrichelt: Debezium streamt Bestelländerungen aus dem WAL des Monolithen durch Kafka, und die Anti-Corruption-Layer bildet sie auf das neue Modell ab.
  • Die Routentabelle listet Ziel, Anteil und Phase pro Route auf, sodass ein Rollback eine einzige Änderung ist.

Strangler-Fig-Migrationen klar zeichnen

  • Ein Diagramm pro Phase, oder ein datierter Titel; hebe die alten auf, um die Richtung zu zeigen.
  • Beschrifte Routen mit Pfad und Anteil, etwa „GET /orders/* · 25 %“.
  • Zeige die Datenhoheit: das System of Record und die Richtung jedes Syncs.
  • Färbe nach Status (umgezogen, Canary, Legacy), nicht nach Team.
  • Lass Interna weg. Das ist eine Migrationskarte, kein Komponentendiagramm.
  • Zeichne versteckte Konsumenten ein, etwa einen nächtlichen Report, der die Datenbank des Monolithen liest.

Das Microservices-Architekturdiagramm zeigt, wie du den Endzustand lesbar hältst.

In Archboard nachbauen

  1. Melde dich mit Google oder GitHub an und erstelle ein Diagramm.
  2. Füge eine Person aus dem Pack C4 Model für die Kunden und ein API-Gateway aus dem Pack Cloud für die Fassade hinzu.
  3. Füge drei Container-Nodes aus dem Pack C4 Model hinzu: Catalog service, Orders service und Legacy monolith. Färbe sie grün, bernsteinfarben und grau.
  4. Füge rechts neben jedem Container einen Postgres-Node aus dem Pack Daten hinzu.
  5. Stapele zwischen der Datenbank des Monolithen und der Orders-Datenbank einen Kafka-Node aus dem Pack Daten (Debezium + Kafka) und eine Komponente aus dem Pack C4 Model (Anti-corruption layer) und verbinde sie nach oben mit gestrichelten Kanten.
  6. Zeichne die Routen von der Fassade aus, beschriftet mit Pfad und Anteil, und füge über jedem Container ein Text-Statuslabel aus dem Pack Basis hinzu.
  7. Füge einen Markdown-Block aus dem Pack Basis mit der Routentabelle hinzu.

Wenn deine Routenkarte in einem Mermaid-Flowchart liegt, macht der Mermaid-Konverter daraus einen bearbeitbaren ersten Entwurf.

Das Beispiel nutzt 11 Diagramm-Nodes und bleibt damit im Free-Plan.

FAQ

Was ist das Strangler-Fig-Pattern? Eine Methode, ein Altsystem schrittweise zu ersetzen: Eine Fassade leitet jede Anfrage an den alten Code oder an einen neuen Service, Fähigkeiten wandern einzeln, und das alte System wird abgeschaltet, wenn nichts mehr dorthin geroutet wird.

Warum heißt es Strangler-Fig-Pattern? Martin Fowler benannte es 2004 nach Würgefeigen, die er in Queensland sah und die um einen Wirtsbaum wachsen und ihn ersetzen können. 2019 ergänzte er Fig im Namen, um die Pflanze zu betonen.

Wie gehst du in einer Strangler-Fig-Migration mit der Datenbank um? In Schritten. Der neue Service liest zuerst die Tabellen des Monolithen, bekommt dann eine eigene Datenbank, die per Change Data Capture gespeist wird, und übernimmt nach der Validierung die Schreibzugriffe. Vermeide Dual Writes aus dem Anwendungscode.

Was ist der Unterschied zwischen Strangler Fig und Branch by Abstraction? Das Strangler Fig routet Anfragen am Rand des Systems. Branch by Abstraction arbeitet im Code: Ein Interface lässt alte und neue Implementierung nebeneinander bestehen, bis jeder Aufrufer umgestellt ist. Viele Migrationen nutzen beides.

Wann solltest du das Strangler-Fig-Pattern nicht einsetzen? Wenn Anfragen an das alte System nicht abgefangen werden können, wenn das System klein genug ist, um es auf einmal zu ersetzen, oder wenn es schnell abgeschaltet werden muss.

Kostenlos starten — ein Seat, keine Kreditkarte.

Ähnliche Beiträge

← Zurück zum Blog

In einer Minute ein Canvas öffnen

Melden Sie sich mit Google oder GitHub an. Kostenlos für einen Sitzplatz. Upgraden Sie, wenn das Team dazukommt.