Graceful Monolith Decoupling mit dem Strangler Fig Muster
Olivia Novak
Dev Intern · Leapcell

Einleitung
In der sich rasant entwickelnden Landschaft der Softwareentwicklung werden monolithische Anwendungen, einst das Fundament der Unternehmensarchitektur, oft zu Engpässen. Ihre inhärente, eng gekoppelte Natur kann Agilität, Skalierbarkeit und Wartbarkeit behindern. Da Organisationen nach größerer Flexibilität und schnelleren Lieferzyklen streben, wächst die Anziehungskraft von Microservices-Architekturen. Ein direkter Sprung in eine vollständige Neuentwicklung von einem Monolithen zu Microservices ist jedoch ein gefährliches Unterfangen, das mit Risiken, hohen Kosten und potenziellen Projektfehlschlägen verbunden ist. Hier kommt das Strangler Fig Muster als pragmatische und effektive Strategie zum Einsatz. Es bietet einen strukturierten, inkrementellen Ansatz zur Zerlegung einer monolithischen Anwendung in eine Suite von unabhängig deploybaren Diensten, wodurch Störungen und Risiken minimiert werden. Dieser Artikel wird sich mit dem Strangler Fig Muster befassen und untersuchen, wie Backend-Teams es nutzen können, um sicher und erfolgreich von monolithischen Giganten zu agilen Microservices-Ökosystemen zu wechseln.
Hauptteil
Bevor wir tiefer eintauchen, wollen wir ein klares Verständnis einiger Schlüsselbegriffe festlegen, die für unsere Diskussion zentral sind:
- Monolithische Anwendung: Eine Softwareanwendung, die als eine einzige, in sich geschlossene Einheit konzipiert ist, bei der alle Komponenten eng gekoppelt sind und innerhalb eines einzigen Prozesses ausgeführt werden.
- Microservices-Architektur: Ein Architekturstil, der eine Anwendung als Sammlung lose gekoppelter, unabhängig bereitstellbarer Dienste strukturiert, von denen jeder für eine bestimmte Geschäftsfunktion verantwortlich ist.
- Strangler Fig Muster: Eine inkrementelle Refactoring-Technik, bei der neue Systemfunktionalitäten als Microservices neben dem bestehenden Monolithen aufgebaut werden. Im Laufe der Zeit werden Aufrufe an den Monolithen für bestimmte Funktionalitäten auf die neuen Microservices umgeleitet, wodurch der alte Monolith effektiv „erstickt“ wird, bis er vollständig stillgelegt werden kann. Dieses Muster ist vom Würgefeigenbaum inspiriert, der um einen Wirtsbaum herum wächst und ihn schließlich ersetzt.
- Anti-Corruption Layer (ACL): Eine Schutzschicht, die die Kommunikation zwischen einem Legacy-System (dem Monolithen) und einem neuen System (Microservice) übersetzt, um sicherzustellen, dass das Domänenmodell des neuen Systems von den Komplexitäten oder Inkonsistenzen des Legacy-Systems unberührt bleibt.
Das Prinzip des Strangler Fig
Das Kernprinzip hinter dem Strangler Fig Muster ist der schrittweise Austausch. Anstatt einer „Big Bang“-Neuentwicklung, bei der das bestehende System aufgegeben und ein neues von Grund auf neu erstellt wird, befürwortet das Strangler Fig Muster den Aufbau neuer Funktionalitäten oder die Portierung bestehender Teile als Microservices neben dem Monolithen. Der Datenverkehr wird dann schrittweise vom Monolithen zu diesen neuen Diensten umgeleitet.
Stellen Sie sich eine monolithische E-Commerce-Anwendung vor, die Benutzerauthentifizierung, Produktkatalog, Auftragsabwicklung und Zahlungen abwickelt. Nach dem Strangler Fig Muster würden Sie nicht das gesamte System auf einmal neu schreiben. Stattdessen könnten Sie das Modul „Benutzerauthentifizierung“ als guten Kandidaten für die Extraktion identifizieren.
- Einen Bounded Context identifizieren: Ermitteln Sie einen bestimmten Funktionsbereich innerhalb des Monolithen, der natürlich isoliert und als Microservice entwickelt werden kann. Die Authentifizierung ist aufgrund ihrer klaren Grenzen ein gängiger Ausgangspunkt.
- Den neuen Dienst aufbauen: Entwickeln Sie einen neuen Microservice, der nur für die Benutzerauthentifizierung zuständig ist. Dieser Dienst wird seine eigene Datenbank, API und Deployment-Pipeline haben.
- Eine Fassade/API-Gateway implementieren: Führen Sie ein API-Gateway oder eine Fassadenschicht ein, die sich vor dem Monolithen und dem neuen Microservice befindet. Zunächst könnten alle Anfragen über den Monolithen laufen.
- Datenverkehr umleiten: Leiten Sie schrittweise Anfragen für die identifizierte Funktionalität (z. B.
/api/auth/*) vom Monolithen an den neuen Microservice um. Diese Umleitung kann auf verschiedenen Ebenen erfolgen:- Load Balancer/Reverse Proxy: Konfigurieren Sie Ihren Load Balancer (z. B. Nginx, HAProxy), um bestimmte URL-Pfade an den neuen Dienst weiterzuleiten.
- Anwendungsseitige Fassade: Innerhalb des Monolithen selbst erstellen Sie ein neues Modul, das als Proxy fungiert und für bestimmte Vorgänge den neuen Microservice aufruft. Dies ist oft Teil der Anti-Corruption Layer.
- Verfeinern und Wiederholen: Wenn der neue Dienst ausgereift und stabil ist, können weitere Funktionalitäten extrahiert werden. Der Prozess wird iterativ auf andere Bounded Contexts (z. B. Produktkatalog, Auftragsverwaltung) angewendet, bis der Monolith schließlich auf einen minimalen Kern reduziert oder vollständig ersetzt wird.
Praktisches Implementierungsbeispiel
Betrachten wir ein einfaches monolithisches Benutzermanagementsystem, das in Python mit Flask geschrieben ist und das wir mit dem Strangler Fig Muster refaktorieren wollen.
Ursprüngliche Monolith-Struktur (vereinfachtes Python/Flask)
# monolith_app.py from flask import Flask, request, jsonify app = Flask(__name__) users_db = { "john.doe": {"password": "password123", "email": "john@example.com"}, "jane.smith": {"password": "securepwd", "email": "jane@example.com"} } @app.route('/users/<username>', methods=['GET']) def get_user(username): user_data = users_db.get(username) if user_data: return jsonify({"username": username, "email": user_data["email"]}), 200 return jsonify({"message": "User not found"}), 404 @app.route('/auth/login', methods=['POST']) def login(): data = request.json username = data.get('username') password = data.get('password') user_data = users_db.get(username) if user_data and user_data['password'] == password: return jsonify({"message": "Login successful", "token": "dummy_jwt_token"}), 200 return jsonify({"message": "Invalid credentials"}), 401 if __name__ == '__main__': app.run(port=5000)
Nun extrahieren wir die login-Funktionalität in einen separaten Microservice.
Neuer Authentifizierungs-Microservice (authentication_service.py)
# authentication_service.py from flask import Flask, request, jsonify auth_app = Flask(__name__) # Dieser Microservice hätte seine eigene Benutzerdatenbank, getrennt von der des Monolithen # Zur Demonstration verwenden wir eine vereinfachte Version. auth_users_db = { "john.doe": {"password": "password123"}, "jane.smith": {"password": "securepwd"} } @auth_app.route('/authenticate', methods=['POST']) def authenticate_user(): data = request.json username = data.get('username') password = data.get('password') user_data = auth_users_db.get(username) if user_data and user_data['password'] == password: # In einem realen Szenario würde dies einen ordnungsgemäßen JWT generieren und zurückgeben return jsonify({"message": "Authentication successful", "token": f"auth_token_for_{username}"}), 200 return jsonify({"message": "Invalid authentication credentials"}), 401 if __name__ == '__main__': auth_app.run(port=5001) # Auf einem anderen Port ausführen
Einführung eines API-Gateways (oder Reverse-Proxy-Konfiguration)
Anstatt den login-Endpunkt des Monolithen direkt zu ändern, würden wir ein API-Gateway (z. B. Nginx, Kong oder sogar einen einfachen Python Flask-Proxy) einführen, um den Datenverkehr weiterzuleiten.
Nginx Konfigurationsbeispiel (simuliert API Gateway)
# nginx.conf relevanter Ausschnitt http { upstream monolith_backend { server 127.0.0.1:5000; } upstream auth_service_backend { server 127.0.0.1:5001; } server { listen 80; # Leitet /api/auth/login an den neuen Authentifizierungsdienst weiter location /api/auth/login { proxy_pass http://auth_service_backend/authenticate; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Alle anderen /api-Anfragen gehen an den Monolithen location /api/ { proxy_pass http://monolith_backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }
Mit dieser Nginx-Konfiguration wird jede Anfrage an http://your-domain/api/auth/login an den neuen Authentifizierungs-Microservice auf Port 5001 weitergeleitet (zum /authenticate-Endpunkt zugeordnet). Alle anderen /api/-Anfragen gehen weiterhin an den ursprünglichen Monolithen auf Port 5000.
Anti-Corruption Layer in der Praxis
Wenn der neue Microservice zurück in den Monolithen aufrufen muss oder umgekehrt, ist eine Anti-Corruption Layer (ACL) entscheidend. Wenn beispielsweise der Authentifizierungsdienst Benutzerdetails benötigt, die sich noch ausschließlich in der Datenbank des Monolithen befinden, könnte eine ACL innerhalb des Authentifizierungsdienstes implementiert werden, um diese Daten abzurufen.
# Innerhalb der Logik von authentication_service.py oder einem dedizierten Client-Modul import requests MONOLITH_BASE_URL = "http://127.0.0.1:5000" def get_user_details_from_monolith(username): """ Anti-Corruption Layer-Funktion zum Abrufen von Benutzerdetails vom Monolithen. Übersetzt die Datenstruktur des Monolithen in das erwartete Format des Authentifizierungsdienstes. """ try: response = requests.get(f"{MONOLITH_BASE_URL}/users/{username}") response.raise_for_status() # Löst eine Ausnahme für fehlerhafte Statuscodes aus monolith_data = response.json() # Übersetzt die Monolithen-Struktur in das Domänenmodell des Authentifizierungsdienstes # Beispiel: Monolith gibt 'email' zurück, Authentifizierungsdienst kümmert sich vielleicht nur um 'username' return {"username": monolith_data.get("username"), "email_from_monolith": monolith_data.get("email")} except requests.exceptions.RequestException as e: print(f"Fehler beim Abrufen des Benutzers vom Monolithen: {e}") return None # Dann in authenticate_user: # ... # user_data = auth_users_db.get(username) # if not user_data: # monolith_user_info = get_user_details_from_monolith(username) # if monolith_user_info and monolith_user_info.get("email_from_monolith"): # # Benutzer potenziell migrieren oder nur seine Details für einen einmaligen Login verwenden # pass # Benutzer im Monolithen gefunden, Login verarbeiten # ...
Diese ACL stellt sicher, dass der Authentifizierungs-Microservice nicht direkt die internen Datenstrukturen des Monolithen konsumiert und so seine Unabhängigkeit und sein sauberes Domänenmodell aufrechterhält.
Anwendungsfälle
Das Strangler Fig Muster ist besonders effektiv in Szenarien wie:
- Legacy-Systeme mit hohem Geschäftswert: Wenn ein Monolith für den Geschäftsbetrieb von kritischer Bedeutung ist, ist eine „Big Bang“-Neuentwicklung zu riskant.
- Schrittweise Modernisierung: Teams möchten für bestimmte Teile des Systems neue Technologien (z. B. neue Sprache, Framework, Datenbank) verwenden, ohne die gesamte Anwendung zu beeinträchtigen.
- Teamautonomie: Ermöglichung unabhängiger Entwicklung und eigener Verantwortung für bestimmte Dienste durch verschiedene Teams, Förderung von Autonomie und schnellerer Iteration.
- Reduzierung technischer Schulden: Systematische Behebung von Bereichen mit hohen technischen Schulden durch deren Ersetzung durch neu erstellte Dienste.
Fazit
Das Strangler Fig Muster bietet eine robuste und praktische Vorgehensweise für die sichere Migration von monolithischen Architekturen zu Microservices. Durch die schrittweise Extraktion, sorgfältige Datenverkehrsumleitung und strategische Nutzung von Anti-Corruption Layers können Organisationen Risiken minimieren, die Geschäftskontinuität wahren und ihre Systeme schrittweise zu einer agileren und skalierbareren Zukunft weiterentwickeln. Dieses Muster fördert im Wesentlichen die Evolution statt der Revolution und macht es zu einer Eckpfeilerstrategie für die Modernisierung von Unternehmensanwendungen.

