Strangler Fig Pattern - Legacy-Systeme schrittweise ablösen Legacy-Modernisierung

Strangler Fig Pattern: Legacy-Systeme schrittweise ablösen

Carola Schulte
Carola Schulte 1. Januar 2025 18 min Lesezeit

Ich habe mehr Big-Bang-Rewrites scheitern sehen als erfolgreich live gehen. Das Strangler Fig Pattern – benannt nach der Würgefeige, die ihren Wirtsbaum langsam umschließt – ist nicht elegant. Aber es ist das Einzige, das unter realem Druck zuverlässig funktioniert. Statt eines riskanten Komplett-Austauschs ersetzen Sie Funktionalität schrittweise, während das alte System weiterläuft.

Kurz gesagt: Das Strangler Fig Pattern ermöglicht kontinuierlichen Geschäftsbetrieb während der Migration. Neue Features werden im neuen System gebaut, alte Funktionen schrittweise migriert – bis das Legacy-System überflüssig wird.

Warum das Strangler Fig Pattern?

Die meisten Legacy-Modernisierungen scheitern nicht an technischen Problemen, sondern an Risiko-Management. Ein Big-Bang-Rewrite bedeutet:

  • Monate ohne Releases: Während des Rewrites gibt es keine neuen Features
  • Hohes Risiko: Am Go-Live-Tag muss alles funktionieren – oder nichts
  • Wissenverlust: Implizites Geschäftswissen im alten Code geht verloren
  • Moving Target: Anforderungen ändern sich während der langen Entwicklung

Das Strangler Fig Pattern löst diese Probleme durch inkrementelle Migration:

Big Bang Rewrite Strangler Fig Pattern
Alles oder nichts Schrittweise Migration
Lange ohne Releases Kontinuierliche Deployments
Hohes Risiko am Go-Live Risiko verteilt auf viele kleine Schritte
Rollback = Komplett-Rollback Rollback pro Feature möglich
Team blockiert für Monate Parallele Feature-Entwicklung möglich

Das Grundprinzip

Die Metapher der Würgefeige beschreibt den Prozess perfekt: Die Feige wächst an einem Wirtsbaum hoch, umschließt ihn langsam, und übernimmt schließlich dessen Platz – während der Wirtsbaum abstirbt.

Übertragen auf Software:

  1. Facade einrichten: Ein Proxy/Gateway leitet Traffic zwischen altem und neuem System
  2. Neue Funktionen im neuen System: Alles Neue wird nur noch modern gebaut
  3. Schrittweise Migration: Bestehende Funktionen werden nach und nach ins neue System übertragen
  4. Legacy abschalten: Wenn keine Requests mehr ans alte System gehen, wird es abgeschaltet

Architektur-Varianten

URL-basiertes Routing

Die einfachste Variante: Ein Reverse Proxy (Nginx, HAProxy) routet basierend auf URL-Pfaden:

# nginx.conf
upstream legacy {
    server legacy-app:8080;
}

upstream modern {
    server modern-app:3000;
}

server {
    listen 80;

    # Neue Module -> modernes System
    location /api/v2/ {
        proxy_pass http://modern;
    }

    location /users/ {
        proxy_pass http://modern;
    }

    # Alles andere -> Legacy
    location / {
        proxy_pass http://legacy;
    }
}
Vorteil: Keine Code-Änderungen am Legacy-System nötig. Das Routing ist transparent für beide Systeme.
Reality-Check: URL-basiertes Routing funktioniert nur, wenn Ihre Endpunkte sauber trennbar sind. In der Realität haben Legacy-Systeme oft ein Wildwuchs an URLs, die kreuz und quer aufeinander verweisen. Ich sehe regelmäßig Setups, wo der Proxy zur Blackbox wird – niemand traut sich mehr, die Routing-Regeln anzufassen.

Feature-Toggle-basiertes Routing

Für feinere Kontrolle: Feature Flags entscheiden zur Laufzeit, welches System einen Request bearbeitet:

class OrderService {
    public function createOrder(array $data): Order {
        if ($this->featureFlags->isEnabled('new-order-system', $this->user)) {
            return $this->modernOrderService->create($data);
        }

        return $this->legacyOrderService->create($data);
    }
}

Das ermöglicht:

  • Canary Releases: Erst 1% der User, dann 10%, dann 50%, dann alle
  • A/B-Testing: Vergleich von altem und neuem System
  • Sofortiges Rollback: Flag ausschalten = zurück zum Legacy-System
Wo es schiefgeht: In der Praxis scheitert Feature-Flag-Routing selten am Tool, sondern an fehlender Ownership für das Umschalten. Flags bleiben ewig auf 50/50 stehen, weil sich niemand traut, die Entscheidung zu treffen. Oder schlimmer: Nach sechs Monaten weiß keiner mehr, welche Flags noch aktiv sind und warum. Feature-Flag-Debt ist real. Ohne klaren Owner pro Modul (inkl. Abschaltentscheidung) wird Strangler Fig zur Dauerbaustelle.

Event-basierte Migration

Für komplexere Szenarien: Beide Systeme reagieren auf Events, das neue System übernimmt schrittweise die Verarbeitung:

// Legacy-System published weiterhin Events
$eventBus->publish(new OrderCreatedEvent($order));

// Modernes System subscribed und baut eigenen State auf
class ModernOrderProjector {
    public function onOrderCreated(OrderCreatedEvent $event): void {
        // Eigene Datenhaltung aufbauen
        $this->repository->save(
            Order::fromLegacyEvent($event)
        );
    }
}
Der Haken: Event-basierte Migration ist die eleganteste Variante auf dem Whiteboard – und die komplexeste in der Praxis. Das Legacy-System muss überhaupt erst Events publishen können. In den meisten Fällen, die ich sehe, fehlt diese Infrastruktur komplett. Dann reden wir plötzlich über „erst mal Event-Bus einführen" – ein Projekt für sich. Und selbst wenn Events existieren: Die Reihenfolge, Idempotenz und Error-Handling sind Minenfelder.

Migrations-Strategien

Asset Capture

Beginnen Sie mit den „Assets" – den wertvollen Teilen des Systems, die am meisten Geschäftswert liefern:

  1. Identifizieren Sie die wichtigsten Geschäftsprozesse
  2. Migrieren Sie diese zuerst ins neue System
  3. Weniger wichtige Funktionen folgen später (oder werden eingestellt)

Anti-Corruption Layer

Ein Anti-Corruption Layer (ACL) übersetzt zwischen den Domänenmodellen von alt und neu:

class LegacyOrderAdapter implements OrderRepositoryInterface {
    public function __construct(
        private LegacyDatabase $legacyDb,
        private OrderMapper $mapper
    ) {}

    public function findById(OrderId $id): ?Order {
        $legacyData = $this->legacyDb->query(
            "SELECT * FROM orders WHERE id = ?",
            [$id->value()]
        );

        if (!$legacyData) {
            return null;
        }

        // Legacy-Datenstruktur in moderne Domain-Objekte übersetzen
        return $this->mapper->toDomainObject($legacyData);
    }
}
Wichtig: Der ACL schützt das neue System vor den „Verschmutzungen" des Legacy-Codes. Ohne ACL schleppen Sie technische Schulden ins neue System mit.

Branch by Abstraction

Führen Sie Abstraktionen ein, bevor Sie migrieren:

  1. Abstraktion einführen: Interface vor die Legacy-Implementierung setzen
  2. Clients umstellen: Alle Aufrufer nutzen das Interface
  3. Neue Implementierung: Moderne Implementierung des Interfaces bauen
  4. Umschalten: Per Feature Flag oder Konfiguration zwischen Implementierungen wechseln
  5. Legacy entfernen: Alte Implementierung löschen

Daten-Migration

Die Datenmigration ist oft der komplexeste Teil. Strategien:

Dual Write

Während der Migration schreiben Sie in beide Systeme:

class OrderService {
    public function createOrder(array $data): Order {
        // Transaktion für Konsistenz
        $this->db->beginTransaction();

        try {
            // Legacy-System
            $legacyOrder = $this->legacyOrderService->create($data);

            // Modernes System
            $modernOrder = $this->modernOrderService->create($data);

            $this->db->commit();

            // Feature Flag entscheidet, welches zurückgegeben wird
            return $this->featureFlags->isEnabled('read-from-modern')
                ? $modernOrder
                : $legacyOrder;

        } catch (Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }
}
Warnung aus der Praxis: Dual Write ist der Punkt, an dem viele Migrationen still sterben: Inkonsistenzen, Phantom-Bugs, nächtliche Datenfixes. Wenn Sie hier keinen Plan haben, verlieren Sie das Vertrauen ins neue System. Besser: Event Sourcing oder Change Data Capture (CDC) für Synchronisation.

Change Data Capture (CDC)

Tools wie Debezium lesen das Transaction Log der Legacy-Datenbank und streamen Änderungen ins neue System:

# docker-compose.yml für Debezium
debezium:
  image: debezium/connect:2.4
  environment:
    BOOTSTRAP_SERVERS: kafka:9092
    GROUP_ID: legacy-cdc
    CONFIG_STORAGE_TOPIC: cdc_configs
    OFFSET_STORAGE_TOPIC: cdc_offsets

# Connector-Konfiguration
{
  "name": "legacy-orders-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "legacy-db",
    "database.port": "5432",
    "database.user": "cdc_user",
    "database.password": "****",
    "database.dbname": "legacy",
    "table.include.list": "public.orders,public.order_items",
    "topic.prefix": "legacy"
  }
}

Typische Fallstricke

1. Zu große Schritte

Das Strangler Fig Pattern funktioniert nur mit kleinen, überschaubaren Schritten. Ich habe Teams gesehen, die „nur noch dieses eine große Feature" ins neue System migrieren wollten – sechs Monate später lief immer noch beides parallel, und keiner wusste mehr, welches System die Wahrheit enthielt.

Lösung: Maximal 2-4 Wochen pro Migrations-Schritt. Lieber öfter deployen.

2. Kein Monitoring

Ohne Metriken wissen Sie nicht, ob die Migration erfolgreich ist:

  • Wie viel Traffic geht noch ans Legacy-System?
  • Wie ist die Error-Rate im neuen System?
  • Gibt es Performance-Unterschiede?

Lösung: Dashboards für beide Systeme mit Vergleichsmetriken.

3. Legacy wird nie abgeschaltet

Das ist der schleichende Tod jeder Migration. Die letzten 10% bleiben ewig im Legacy-System. „Das machen wir später" wird zum Mantra. Zwei Jahre später bezahlen Sie für zwei Systeme, zwei Wartungsverträge, zwei Entwicklerteams – und niemand will mehr an das alte System fassen.

Lösung: Deadline setzen. Verbindlich. Nach Datum X wird Legacy abgeschaltet – notfalls werden verbleibende Features eingestellt. Dieser Termin muss im Management-Report stehen. Und hier die harte Wahrheit: Wenn es keine Entscheidungsebene gibt, die bereit ist, Altlasten wirklich abzuschalten, scheitert jede Migration – egal wie gut die Architektur ist.

4. Fehlende Daten-Konsistenz

Bei Dual Write oder parallelem Betrieb können Daten divergieren. Ich habe Fälle erlebt, wo morgens das Sales-Team andere Zahlen sah als das Controlling – weil ein System drei Sekunden schneller war als das andere. Solche Inkonsistenzen zerstören das Vertrauen in die Migration schneller als jeder Bug.

Lösung: Reconciliation-Jobs, die regelmäßig Daten zwischen beiden Systemen vergleichen und Unterschiede melden. Automatische Alerts, bevor das Business die Differenzen entdeckt.

Praxis-Beispiel: E-Commerce-Migration

Ein mittelständischer Online-Shop mit 15 Jahre altem Monolithen. Die Migration dauerte 18 Monate:

Phase Dauer Migrierte Komponenten
1. Setup 2 Monate API Gateway, CI/CD, Monitoring
2. Produktkatalog 3 Monate Produkte, Kategorien, Suche
3. User-Management 2 Monate Login, Profile, SSO
4. Warenkorb 3 Monate Cart, Checkout, Payment
5. Bestellungen 4 Monate Orders, Fulfillment, Returns
6. Admin & Reporting 3 Monate Backend, Analytics
7. Cleanup 1 Monat Legacy abschalten

Während der gesamten 18 Monate lief der Shop ohne Unterbrechung. Neue Features wurden ab Monat 3 nur noch im neuen System entwickelt.

Hilfreiche Tools

  • API Gateway: Kong, AWS API Gateway, Traefik – für URL-basiertes Routing
  • Feature Flags: LaunchDarkly, Unleash, Flagsmith – für Feature-Toggle-basierte Migration
  • CDC: Debezium, AWS DMS – für Daten-Synchronisation
  • Monitoring: Datadog, Grafana – für Vergleichsmetriken
  • Testing: Contract Testing (Pact), Chaos Engineering – für Migrations-Sicherheit

Fazit

Das Strangler Fig Pattern ist keine Silberkugel – aber es ist der Ansatz, der in der Praxis am häufigsten funktioniert. Der Schlüssel liegt in:

  • Kleine Schritte: Maximal 2-4 Wochen pro Migration
  • Kontinuierlicher Betrieb: Beide Systeme laufen parallel
  • Messbarkeit: Monitoring zeigt Fortschritt und Probleme
  • Commitment: Deadline für Legacy-Abschaltung setzen
Nächster Schritt: In der Praxis scheitert der erste Schritt selten an Technik, sondern an falschen Annahmen über den Legacy-Code. Genau hier setze ich in Rescue- und Audit-Projekten an – mit einer nüchternen Analyse dessen, was wirklich im System steckt. Das Ergebnis ist eine belastbare Migrations-Roadmap – inklusive Risiko-Landkarte und klarer Reihenfolge.

Häufige Fragen

Wie lange dauert eine Strangler Fig Migration?

Je nach Systemgröße 6-24 Monate. Kleinere Systeme schneller, Enterprise-Monolithen länger. Entscheidend ist, dass Sie nach wenigen Wochen den ersten echten Traffic über das neue System routen – nicht nach Monaten.

Kann ich das Pattern auch für Datenbank-Migrationen nutzen?

Ja, aber es ist komplexer. Change Data Capture (CDC) oder Event Sourcing sind hier die bevorzugten Mechanismen. Dual Write nur als letzte Option.

Was, wenn das Legacy-System nicht mehr gewartet werden kann?

Dann ist das Strangler Fig Pattern erst recht die richtige Wahl – Sie können das Legacy-System einfrieren und nur noch das neue System weiterentwickeln.

Brauche ich ein separates Team für die Migration?

Nicht unbedingt. Oft ist es besser, wenn das bestehende Team beides macht – so bleibt das Wissen über das Legacy-System erhalten und fließt in die neue Implementierung ein.


Carola Schulte

Carola Schulte

Software-Architektin mit 25+ Jahren Erfahrung in Legacy-Modernisierung, Security-Audits und System-Design.

Beratung anfragen

Legacy-System modernisieren?

Ich analysiere Ihr bestehendes System und entwickle eine Migrations-Strategie – ohne Risiko für den laufenden Betrieb.

Kostenlose Erstanalyse anfragen