Das Strangler-Fig-Pattern: Legacy-Monolithen sicher ablösen
Ihr Legacy-Monolith verarbeitet jeden Monat Transaktionen in Millionenhöhe. Er ist hässlich, langsam und wird von undokumentierten Workarounds zusammengehalten, die nur eine einzige Entwicklerin vollständig versteht – und die hat das Unternehmen im März verlassen. Der CTO will modernisieren. Der Vorstand will modernisieren. Alle sind sich einig, dass sich das System ändern muss.
Also schlägt das Team vor, was als offensichtliche Lösung erscheint: ein vollständiges Rewrite. Neues Framework, neue Architektur, alles neu. Achtzehn Monate später liegt das Rewrite hinter dem Zeitplan, das Legacy-System bedient weiterhin den Produktionsverkehr, und das Team pflegt zwei Codebasen statt einer. Diese Geschichte spielt sich in der europäischen Softwarebranche mit deprimierender Regelmäßigkeit ab.
Einen Legacy-Monolithen abzulösen erfordert kein Big-Bang-Rewrite. Das Strangler-Fig-Pattern bietet einen grundlegend anderen Ansatz. Statt den Monolithen auf einen Schlag zu ersetzen, lassen Sie ein neues System um ihn herum wachsen – und leiten den Traffic schrittweise von alten Komponenten zu neuen, bis das ursprüngliche System vollständig ersetzt ist. Der Monolith hört nie auf zu laufen. Die Feature-Entwicklung hört nie auf. Und jeder Schritt der Migration liefert funktionierende Software in Produktion.
Warum vollständige Rewrites scheitern
Bevor wir das Strangler-Fig-Pattern im Detail betrachten, lohnt es sich zu verstehen, warum die Alternative – das vollständige Rewrite – so beständig scheitert.
Ein funktionierender Monolith kodiert, so hässlich er auch sein mag, Jahre an Geschäftslogik, Sonderfällen und impliziten Entscheidungen. Vieles davon ist nicht dokumentiert. Manches ist nicht einmal beabsichtigt – es entstand aus Bugfixes und Workarounds, auf die sich Nutzer heute verlassen. Wenn ein Team mit einem Rewrite beginnt, baut es nicht nur den Code neu; es versucht, Jahre an institutionellem Wissen aus einem System zurückzuentwickeln, das sich aktiv dagegen sträubt, verstanden zu werden.
Das Ergebnis ist, was Fred Brooks vor Jahrzehnten beschrieben hat und was Teams noch heute erleben: Das Rewrite dauert länger als geschätzt, es übersieht kritische Geschäftsregeln, und bis es live geht, haben sich die Geschäftsanforderungen verschoben. Währenddessen benötigt das Legacy-System weiterhin Wartung, Sicherheitspatches und gelegentliche Feature-Erweiterungen – was die Zeit desselben Teams verbraucht und eine Doppelpflege-Last erzeugt, die die Velocity aufzehrt.
Eine Legacy-PHP-Migration muss diesen Weg nicht gehen. Das Strangler-Fig-Pattern erkennt eine Wahrheit an, die Rewrite-Befürworter oft übersehen: Ein laufendes System, das Umsatz generiert, ist wertvoller als ein halbfertiger Ersatz, der nichts generiert.
Das Strangler-Fig-Pattern erklärt
Der Name stammt von der Würgefeige (englisch strangler fig), die um einen Wirtsbaum herumwächst und ihn allmählich umschließt, bis der Wirtsbaum abstirbt und die Feige an seiner Stelle steht. Die zentrale biologische Erkenntnis, die sich auf Software übertragen lässt: Der Wirtsbaum funktioniert während des gesamten Prozesses weiter. Er sorgt weiterhin für Struktur und Nährstofftransport, selbst während die Feige um ihn herumwächst.
In Software-Begriffen funktioniert das Pattern in drei Phasen.
Phase 1: Abfangen. Platzieren Sie eine Routing-Schicht – einen Reverse Proxy, ein API-Gateway oder eine Middleware-Komponente – vor den Monolithen. Aller Traffic fließt weiterhin zum Legacy-System, aber die Routing-Schicht gibt Ihnen die Möglichkeit, bestimmte Requests an neue Services umzuleiten.
Phase 2: Implementieren. Bauen Sie einen Ersatz für einen Bounded Context oder einen Funktionsbereich im neuen Stack. Dieser Ersatz muss dieselben Eingaben verarbeiten und dieselben Ausgaben erzeugen wie die Legacy-Komponente, die er ablöst. Sobald er Integrationstests gegen reale Produktionsdatenformen besteht, leiten Sie den Traffic für dieses spezifische Feature vom Monolithen zum neuen Service.
Phase 3: Stilllegen. Sobald aller Traffic für ein Feature an den neuen Service geleitet wurde und der neue Service über einen definierten Zeitraum stabil in Produktion lief, entfernen Sie den entsprechenden Code aus dem Monolithen. Der Monolith schrumpft mit jedem Migrationszyklus.
Wiederholen Sie Phase 2 und 3, bis der Monolith keine verbleibenden Zuständigkeiten mehr hat und vollständig außer Betrieb genommen werden kann.
Die Routing-Schicht einrichten
Die Routing-Schicht ist die kritische Infrastruktur, die eine inkrementelle Migration überhaupt erst möglich macht. Ohne sie sind Sie zu einem Big-Bang-Cutover gezwungen – genau das Risiko, das das Strangler-Fig-Pattern vermeiden soll.
Für PHP-Monolithen ist der praktischste Ansatz ein Nginx-Reverse-Proxy, der anhand von URL-Pfad oder Header-Werten routet:
# nginx.conf — Strangler-Fig-Routing
upstream legacy_monolith {
server 10.0.1.10:80;
}
upstream new_order_service {
server 10.0.2.20:8080;
}
server {
listen 80;
server_name app.example.com;
# Migriert: Bestellverarbeitung läuft jetzt über den neuen Service
location /api/orders {
proxy_pass http://new_order_service;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
# Alles andere geht weiterhin an den Monolithen
location / {
proxy_pass http://legacy_monolith;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
Diese Konfiguration leitet allen /api/orders-Traffic an den neuen Order-Service, während alles andere weiterhin das Legacy-System erreicht. Eine neue Migration hinzuzufügen ist so einfach wie das Ergänzen eines neuen location-Blocks. Ein Rollback ist so einfach wie dessen Entfernen.
Für feingranularere Kontrolle – Routing anhand von Request-Headern, Nutzersegmenten oder prozentualer Traffic-Aufteilung – bieten Traefik oder ein API-Gateway wie Kong reichhaltigere Routing-Semantik, ohne dass Nginx-Konfigurationen neu geladen werden müssen.
Was zuerst migrieren?
Nicht alle Teile eines Monolithen sind gleich gute Kandidaten für eine frühe Migration. Die erste Extraktion sollte idealerweise eine Komponente sein, die drei Kriterien erfüllt.
Klare Domänengrenzen. Die Komponente hat wohldefinierte Ein- und Ausgaben, begrenzten geteilten Zustand mit anderen Komponenten und eine kohärente Geschäftsverantwortung. Ein Bestellverarbeitungs-Modul, das Bestellanfragen liest und Bestelldatensätze schreibt, ist ein besserer erster Kandidat als ein Authentifizierungssystem, das jedes andere Modul berührt.
Hohe Änderungsfrequenz. Komponenten, die das Unternehmen schnell weiterentwickeln muss, profitieren am meisten davon, in eine moderne Codebasis extrahiert zu werden. Wenn das Produktteam ein Backlog an bestellbezogenen Features hat, das Rechnungsmodul aber seit zwei Jahren stabil ist, fangen Sie mit den Bestellungen an.
Beherrschbare Datenkopplung. Komponenten mit eigenem logischem Datenspeicher oder einem minimalen Satz geteilter Tabellen lassen sich leichter extrahieren als Komponenten, die tief mit einem geteilten Datenbankschema verflochten sind. Die Datenmigrations-Story zählt ebenso sehr wie die Code-Migrations-Story.
Ein häufiger Fehler ist, zuerst die risikoreichste Komponente zu wählen, um „es hinter sich zu bringen". Das ist verkehrt. Die erste Extraktion ist der Ort, an dem das Team die Migrationsmechanik lernt – Routing, Datensynchronisation, Teststrategien, Deployment-Koordination. Dieses Lernen an der geschäftskritischsten Komponente durchzuführen, ist unnötig riskant. Beginnen Sie mit etwas, das wichtig genug ist, um den Aufwand zu rechtfertigen, aber abgegrenzt genug, um den Schadensradius zu begrenzen.
Ein PHP-zu-Symfony-Migrations-Playbook
Betrachten wir ein konkretes Szenario: eine Legacy-PHP-5.6-Anwendung ohne Framework, mit prozeduralem Code und einer MySQL-Datenbank. Das Team möchte zu PHP 8.3 mit Symfony 7 migrieren. So lässt sich das Strangler-Fig-Pattern anwenden.
Schritt 1: Inventarisieren und kartieren
Bevor Sie neuen Code schreiben, kartieren Sie die URL-Routen des Monolithen auf die zugehörigen PHP-Dateien und Datenbanktabellen. In einer frameworklosen PHP-Anwendung bedeutet das oft, die .htaccess- oder Nginx-Rewrite-Regeln zu lesen und jede URL zu ihrem Handler-Skript zurückzuverfolgen.
# Schnelle Routenkarte aus Nginx-Config oder .htaccess erzeugen
grep -r 'RewriteRule' .htaccess | awk '{print $2, $3}'
# Mit der Datenbank abgleichen
grep -rn 'mysql_query\|mysqli_query\|PDO' --include='*.php' | head -40
Die Ausgabe liefert Ihnen eine Abhängigkeitskarte: welche URLs welche PHP-Dateien treffen, welche PHP-Dateien welche Datenbanktabellen berühren. Diese Karte ist das Planungsdokument der Migration.
Schritt 2: Den Proxy deployen
Richten Sie Nginx oder Traefik vor dem Monolithen ein. Anfangs läuft 100 % des Traffics weiterhin zum Legacy-System durch. Der Proxy fügt noch keine Funktionalität hinzu – er etabliert die Routing-Schicht, die zukünftige Migrationen nutzen werden.
Stellen Sie sicher, dass der Proxy keine Regressionen einführt, indem Sie Ihre vorhandene Testsuite (sofern vorhanden) ausführen oder Fehlerraten und Antwortzeiten 48 Stunden lang überwachen.
Schritt 3: Den ersten Service extrahieren
Bauen Sie den Ersatz-Service in Symfony. Für das Bestellverarbeitungs-Beispiel:
// src/Controller/OrderController.php
#[Route('/api/orders', name: 'orders_')]
class OrderController extends AbstractController
{
public function __construct(
private readonly OrderService $orderService,
private readonly LoggerInterface $logger,
) {}
#[Route('', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$payload = json_decode($request->getContent(), true);
try {
$order = $this->orderService->createOrder($payload);
return $this->json($order, Response::HTTP_CREATED);
} catch (ValidationException $e) {
return $this->json(['errors' => $e->getErrors()], Response::HTTP_UNPROCESSABLE_ENTITY);
}
}
#[Route('/{id}', methods: ['GET'])]
public function show(int $id): JsonResponse
{
$order = $this->orderService->findOrFail($id);
return $this->json($order);
}
}
Die kritische Anforderung: Dieser Service muss für dieselben Eingaben identische Antworten wie das Legacy-System erzeugen. Schreiben Sie Integrationstests, die reale Produktions-Request/Response-Paare gegen den neuen Service wiederholen, um die Verhaltensäquivalenz zu verifizieren.
Schritt 4: Shadow-Traffic
Bevor Sie reale Nutzer an den neuen Service leiten, fahren Sie Shadow-Traffic: Duplizieren Sie eingehende Requests sowohl an das Legacy-System als auch an den neuen Service, vergleichen Sie die Antworten, geben aber nur die Legacy-Antwort an den Nutzer zurück. Das deckt Verhaltensunterschiede auf, ohne Nutzer zu beeinträchtigen.
In Nginx erreichen Sie das mit der mirror-Direktive:
location /api/orders {
mirror /mirror-orders;
proxy_pass http://legacy_monolith;
}
location = /mirror-orders {
internal;
proxy_pass http://new_order_service$request_uri;
}
Protokollieren und vergleichen Sie die Antworten beider Systeme. Beheben Sie Abweichungen im neuen Service, bis er das Verhalten des Legacy-Systems für mindestens 99,9 % der Requests trifft.
Schritt 5: Schrittweiser Cutover
Sobald das Shadow-Testing die Verhaltensäquivalenz bestätigt, leiten Sie einen kleinen Prozentsatz des realen Traffics an den neuen Service. Beginnen Sie bei 5 %, überwachen Sie Fehlerraten und Antwortzeiten und steigern Sie schrittweise. Bei Wolf-Tech folgen wir typischerweise einer Progression von 5 % → 25 % → 50 % → 100 % mit jeweils 24–48 Stunden pro Stufe.
Wenn Fehler in einer Stufe in die Höhe schnellen, leiten Sie den Traffic sofort zurück zum Monolithen. Die Routing-Schicht macht dies zu einer Konfigurationsänderung, nicht zu einem Deployment.
Schritt 6: Toten Code entfernen
Sobald der neue Service 100 % des Traffics für das migrierte Feature bedient und mindestens zwei Wochen stabil war, entfernen Sie den entsprechenden Code aus dem Monolithen. Das ist emotional befriedigend und operativ wichtig – toter Code in einem Monolithen sorgt bei Entwicklern für Verwirrung und vergrößert die Angriffsfläche für Sicherheitslücken.
Das Problem der geteilten Datenbank lösen
Der schwierigste Teil des Strangler-Fig-Patterns ist nicht das Routing oder die Code-Extraktion – es ist die Datenbank. Legacy-Monolithen haben typischerweise eine einzige geteilte Datenbank, und die neuen Services benötigen Zugriff auf dieselben Daten.
Drei Ansätze, in der Reihenfolge zunehmender Isolation:
Geteilter Datenbankzugriff. Der neue Service verbindet sich mit derselben Datenbank wie der Monolith. Das ist am schnellsten umzusetzen, koppelt den neuen Service aber an das Legacy-Schema. Es funktioniert als Übergangsschritt, sollte aber nicht der Dauerzustand sein.
Datenbank-View-Schicht. Erstellen Sie Views oder ein Read-only-Replikat, das die Legacy-Daten in der vom neuen Service erwarteten Form präsentiert. Der neue Service liest aus Views und schreibt über eine API, die der Monolith bereitstellt. Das entkoppelt das Datenmodell des neuen Services vom Legacy-Schema.
Datensynchronisation mit späterer Migration. Der neue Service unterhält seine eigene Datenbank. Ein Synchronisationsprozess (Change Data Capture mit Debezium oder Event-Publishing vom Monolithen) hält die neue Datenbank während der Übergangsphase synchron. Sobald alle Konsumenten eines Datensatzes migriert wurden, wird die Synchronisation entfernt und die neue Datenbank wird maßgeblich.
Der richtige Ansatz hängt von der Komplexität der Datenkopplung und dem Zeitplan der Migration ab. Ein Projekt zur Legacy-Code-Optimierung beginnt typischerweise mit geteiltem Datenbankzugriff und entwickelt sich hin zu vollständiger Datenisolation, je mehr Services extrahiert werden.
Häufige Fehler bei Strangler-Fig-Migrationen
Zu viele Dinge auf einmal migrieren. Die Stärke des Patterns liegt in seiner Inkrementalität. Teams, die versuchen, fünf Services gleichzeitig zu extrahieren, führen den Koordinationsaufwand eines Big-Bang-Rewrites wieder ein. Extrahieren Sie einen Service, stabilisieren Sie ihn, lernen Sie aus dem Prozess und extrahieren Sie dann den nächsten.
Die Shadow-Traffic-Phase überspringen. Verhaltensäquivalenz-Tests sind nicht optional. Legacy-Systeme haben über Jahre implizites Verhalten angesammelt – Sonderfälle, Zeitzonenbehandlung, Rundungsregeln, Behandlung von Null-Werten – das die Dokumentation nicht erfasst. Shadow-Traffic deckt diese Abweichungen auf, bevor Nutzer ihnen begegnen.
Den Monolithen während der Migration vernachlässigen. Das Legacy-System bedient während der Migration weiterhin Produktionsverkehr. Alle Wartung und Sicherheitspatches am Monolithen aufzuschieben, weil „wir ihn ohnehin ersetzen", erzeugt sich aufsummierendes Risiko. Halten Sie den Monolithen während des gesamten Prozesses gesund.
Kein Rollback-Plan. Jede Traffic-Routing-Änderung sollte innerhalb von Minuten reversibel sein. Wenn ein Rollback ein Deployment erfordert, ist Ihr Rollback-Plan zu langsam. Konfigurationsbasiertes Routing – nicht Feature-Flags auf Code-Ebene – bietet den schnellsten Rollback-Pfad.
Wie lange dauert eine Strangler-Fig-Migration?
Die Zeitpläne variieren dramatisch je nach Monolith-Größe, Teamfähigkeit und der Tiefe der Kopplung im Legacy-Code. Als grobes Rahmenwerk aus unserer Erfahrung mit europäischen mittelständischen Unternehmen:
Die Extraktion eines einzelnen Bounded Context – von der Bestandsaufnahme über Shadow-Traffic bis zum vollständigen Cutover – dauert typischerweise vier bis acht Wochen für ein Team aus zwei bis drei Senior-Entwicklern. Ein Monolith mit fünf bis acht großen Bounded Contexts kann in neun bis achtzehn Monaten vollständig migriert werden, wobei jede Extraktion unterwegs Produktionswert liefert.
Das ist langsamer als der optimistische Zeitplan eines vollständigen Rewrites. Es ist dramatisch schneller als der realistische Zeitplan, denn Rewrites dauern fast immer das Zwei- bis Dreifache ihrer ursprünglichen Schätzung, während der Strangler-Fig-Ansatz vorhersehbaren, inkrementellen Fortschritt liefert.
Wann das Strangler-Fig-Pattern nicht die richtige Wahl ist
Das Pattern setzt voraus, dass das bestehende System funktioniert und Nutzer bedient. Wenn das Legacy-System grundlegend kaputt ist – Datenkorruption, Sicherheitsverletzungen oder regulatorische Nicht-Konformität, die sich nicht patchen lässt – kann ein aggressiverer Ansatz gerechtfertigt sein.
Ebenso kann die Datenbankmigration den Aufwand so dominieren, dass eine inkrementelle Extraktion kaum Vorteile gegenüber einem strukturierten Ersatz bietet, wenn das Datenmodell des Legacy-Systems so tiefgreifend fehlerhaft ist, dass kein neuer Service damit arbeiten kann.
Diese Fälle sind selten. In den meisten Situationen funktioniert das Legacy-System – es ist nur teuer in der Wartung, langsam in der Weiterentwicklung und läuft auf nicht mehr unterstützter Infrastruktur. Das Strangler-Fig-Pattern ist genau für dieses verbreitete Szenario gemacht.
Fazit
Das Strangler-Fig-Pattern verwandelt die Legacy-Migration von einem risikoreichen Alles-oder-nichts-Glücksspiel in eine Reihe beherrschbarer, reversibler Schritte. Jeder Schritt liefert funktionierende Software in Produktion. Jeder Schritt verringert den Fußabdruck des Monolithen. Und jeder Schritt kann je nach Geschäftsprioritäten pausiert oder beschleunigt werden, ohne den bereits erreichten Fortschritt zu verlieren.
Für CTOs und Tech-Leads, die Legacy-PHP-Systeme verantworten, beseitigt dieser Ansatz die falsche Wahl zwischen „für immer mit dem Monolithen leben" und „das Unternehmen auf ein Rewrite verwetten". Es gibt eine dritte Option: den Ersatz um das bestehende System herum wachsen lassen, einen Bounded Context nach dem anderen, bis der Monolith verschwunden ist und niemand genau bemerkt hat, wann er verschwand.
Wenn Sie eine Migrationsstrategie für Ihr Legacy-System evaluieren: Wolf-Tech ist auf Legacy-Code-Optimierung spezialisiert und hat mehrere europäische Unternehmen durch Strangler-Fig-Migrationen geführt. Kontaktieren Sie uns unter hello@wolf-tech.io oder besuchen Sie wolf-tech.io für ein kostenloses Erstgespräch.

