TL;DR
- Self-contained Systems (SCS) teilen komplexe fachliche Systeme in weitgehend unabhängig deploybare und betreibbare Subsysteme auf.
- Zur Migration des Bestandssystems in eine SCS-Architektur analysieren Methoden wie das Event Storming die fachlichen Bestandteile.
- Techniken des strategischen Domain-driven Design helfen, den Monolithen in potenzielle SCS zu zerlegen.
- Die Migration sollte die fachlichen Subsysteme zuerst angehen, die sich einfach mit dem Altsystem verbinden lassen und einen hohen Geschäftswert liefern.
- Zum Verbinden von Alt- und Neusystemen kommen UI-Inklusion, Messaging oder synchrone Aufrufe anderer Systeme (vorzugsweise in dieser Reihenfolge) zum Einsatz.
Legacy-System – kaum ein anderer Begriff schürt mehr Abneigung und Furcht im Herzen von Softwareentwicklerinnen und -entwicklern. Was einmal als flexible und einfache Anwendung begann, wuchs über die Jahre zu einem unüberschaubaren Monolithen heran, der sich nur noch unter größtem Aufwand weiterentwickeln, testen und deployen lässt. Eine solche Anwendung ist ein typisches Beispiel für ein schlecht oder gar nicht modularisiertes System, bei dem eine Anpassung an einer Stelle weitere an anderen Stellen nach sich zieht. Architektinnen und Architekten sprechen von starker Kopplung. Sind alle Funktionen in einen großen, unstrukturierten Block Code gegossen, ist der Kopplungseffekt auch innerhalb dessen zu beobachten. Anpassungen können dann zu einem Fehlverhalten an einer unerwarteten, anderen Stelle führen.
Zerlegt man den Monolithen in lose gekoppelte Module, haben lokale Änderungen keine Anpassungen in anderen Systemen mehr zur Folge. Werden diese Module in Form von autonomen, aber miteinander integrierten Systemen umgesetzt, erhält man zusätzlich den Vorteil, dass sie weitgehend unabhängig voneinander deployt werden können. Dieser Ansatz ist der bekannte Microservices-Architekturstil [1], dem viele Architektinnen und Architekten allerdings inzwischen mit Skepsis gegenüberstehen, da die Komplexität schnell wächst. Daneben gibt es noch einen einfacheren Ansatz, der ähnliche und noch weitere Vorteile verspricht: Self-contained Systems (SCS).
Was sind Self-contained Systems?
Die Self-contained-Systems-Architektur legt großen Wert auf eine hohe Unabhängigkeit verschiedener Module. Diese lose gekoppelte Systemarchitektur (siehe Kasten 1 „Kleine Bausteine, lose verbunden“) verspricht, dass Teams einzelne Teile weitgehend unabhängig entwickeln und deployen. Während die relativ vage Definition eines Microservice als „unabhängig deploybarer Service, modelliert um eine fachliche Domäne“ [1] große Freiräume bei der Wahl des Architekturstils lässt, sind die Kriterien für Self-contained Systems klarer umrissen.
Wie Microservices sind sie mit einer streng gezogenen fachlichen Grenze versehen, häufig handelt es sich dabei jedoch um einen kompletten Bounded Context – also einen abgeschlossenen Bereich mit einer einheitlichen, aus der Fachdomäne stammenden Sprachgebung – im Sinne des strategischen Domain-driven Design (DDD). Dadurch sind sie tendenziell größer als Microservices, sodass weniger Aufwand für Integration und Infrastruktur anfällt.
Zudem gibt es für SCS eine Reihe an Best Practices zur Systemintegration. SCS sollen bevorzugt auf Nachrichten nach dem Credo „fire and forget“ setzen und nach dem Versenden nicht auf Antwort warten. Synchrone Request-Response-Kommunikation (zum Beispiel über RESTful-Services) sollte vermieden werden. Dann entsteht ein System, das bei Teilausfällen sehr resilient reagiert.
Die Nutzeroberfläche wird stets als ein Teil des Self-contained System behandelt (eine Idee, die im Rahmen der Micro Frontends ebenfalls in der Microservices-Welt Fuß gefasst hat). In erster Linie bleibt hierdurch mehr Arbeit innerhalb eines Teams und es entstehen keine Frontend-Monolithen als Flaschenhälse. Ein weiterer, oft überraschender Effekt ist, dass sich in vielen Fällen ein Austausch von Datenstrukturen zwischen UIs und die dadurch entstehende fachliche Kopplung über geteilte Datenformate komplett vermeiden lässt. Entwickler können stattdessen Teile der Nutzeroberflächen des einen Systems in einem anderen verwenden.
Damit die unabhängige Entwicklung der verschiedenen SCS nicht in einem unterschiedlichen Look and Feel oder einem Wildwuchs von Integrationsansätzen endet, fordert die SCS-Architektur von allen Systemen, sich an eine gemeinsame, koordinierte Makroarchitektur zu halten. Dazu gehören beispielsweise die Verwendung bestimmter Integrationstechniken, UI-Komponenten oder Styleguides. Sie ermöglichen ein weitgehend reibungsloses Zusammenspiel im System of Systems.
Gerade die Best Practices zur Systemintegration eröffnen Migrationspfade, um eine gewachsene Architektur in kleinen, inkrementellen Schritten zu einer SCS Architektur umzubauen. Eine Umstellung nach dem Big-Bang-Muster ist nach Erfahrung des Autors nur in seltenen Fällen empfehlenswert oder wirtschaftlich.
Eine starke Kopplung kann viele Formen annehmen, von eher technischen wie „Ich muss genau wissen, auf welcher IP dieser Dienst läuft“ oder „Jedes Mal, wenn ein Feld dazu kommt, muss ich überall die Deserialisierungslogik updaten“ bis hin zu einer sehr subtilen Art der Kopplung: „Wenn ich fachlich an System A etwas ändere, muss auch zwangsweise System B angepasst werden“. Zum Bau weitgehend unabhängiger Systeme ist vor allem die zuletzt genannte Form hinderlich. Es ist deshalb nicht nur notwendig, die technischen Implementierungsdetails zu abstrahieren, um so wenig Artefakte der Infrastruktur (Datenbank, Frontend und weitere) wie möglich zwischen Systemen zu teilen. Vielmehr müssen Schnittstellen zwischen Systemen gebildet werden, die möglichst viele der fachlichen Implementierungsdetails verstecken. Diese Empfehlung ist nicht neu, sondern geht auf die Arbeit von David Parnas in den 70er-Jahren zurück [2]. Um die fachlich abstrahierten Schnittstellen zu finden, bedienen sich Architektinnen und Architekten gerne aus dem Repertoire des strategischen Domain-driven Design [3]. Eine Faustregel für Selfcontained Systems ist, dass jedes System idealerweise einen Bounded Context umsetzt, also einen abgeschlossenen Bereich mit einer einheitlichen, aus der Fachdomäne stammenden Sprachgebung.
Planung der Migration
Der erste Schritt vom Monolithen zu SCS ist es, die Teilstücke zu identifizieren, die sich in ein eigenes SCS überführen lassen. Da die Zielarchitektur aus fachlich geschnittenen Systemen besteht, ist die im Monolithen enthaltene Fachlichkeit genauer zu analysieren. Diesen Schritt sollten Entwicklerinnen und Entwickler nicht auf die leichte Schulter nehmen, da sich bei der Aufteilung der Applikation in ihre verschiedenen Bausteine entscheidet, wie stark oder lose diese gekoppelt sind (siehe Kasten 1 „Kleine Bausteine, lose verbunden“).
Eine Möglichkeit, eine Analyse der Fachdomänen durchzuführen, ist das Event Storming zur Identifikation der Bounded Contexts. Abbildung 1 zeigt das Ergebnis einer solchen Analyse für eine fiktive Car-Sharing-Anwendung. Dort wurden die fachlichen Domänen „Nutzerregistrierung“, „Fuhrparkmanagement“, „Trips“, „Abrechnung“ und „Kundendienst“ identifiziert. Bei der Analyse entstehen bereits erste Ideen, wie die verschiedenen Fachlichkeiten miteinander interagieren und welche Nachrichten sie miteinander austauschen.
Hat sich im Dialog mit den Fachabteilungen eine Aufteilung gefunden, die die gewünschte fachliche, lose Kopplung widerspiegelt, beginnen Architektinnen und Architekten damit, Teile des Altsystems nach und nach durch neue SCS zu ersetzen. Diese arbeiten mit dem Altsystem zusammen, ohne dass Nutzer einen Unterschied feststellen. Das ist eine Herangehensweise, die auch Strangler Fig Application heißt, da eine neue Anwendung wie die namensgebende Würgefeige langsam aber sicher immer größere Teile des alten Wirtssystems überdeckt und es letztendlich abtötet (siehe Abbildung 2).
Ein gutes Kriterium für die Wahl der ersten Fachlichkeit, die aus dem Legacy System herausgelöst wird, ist der dadurch entstehende Geschäftswert [4]. Die Migration fügt beispielsweise dem Legacy-System zusätzliche Funktionen hinzu, zum Beispiel einen Check-in-Prozess sowohl für den Desktop als auch das Smartphone. Fachlichen Wert kann eine Migration auch stiften, wenn sie einen akuten Schmerz mit dem Legacy-System beseitigt: besonders unzuverlässige, fehleranfällige Funktionen. Der Aufwand der Migration ist ein weiteres Kriterium. Spielt die Fachlichkeit an vielen Stellen im Monolithen eine Rolle, kann diese starke Verwebung eine Re-Integration erheblich erschweren.
Beide Kriterien können in einer 2x2-Entscheidungsmatrix kombiniert werden (siehe Abbildung 3). Fachlichkeiten, die sich in der rechten, oberen Ecke befinden, sind gute Kandidaten für ein erstes SCS. Eine weitere Technik zur Analyse, die sich in der DDD-Community immer größerer Beliebtheit erfreut, sind Core Domain Charts.
Das erste SCS integrieren
Hat man sich für einen guten Kandidaten entschieden, ergeben sich eine Reihe technischer Fragen. Gerade hier wirken sich die Stärken der sehr losen Kopplung durch die SCS-Architektur positiv aus. Folgende Möglichkeiten gibt es:
- Link-Integration: Im einfachsten Fall springen Nutzer über einen Link im Altsystem in das neue. Eine kleine Menge von Daten sowie eine Rücksprungadresse für den Erfolgs- oder Fehlerfall können hier als Request-Parameter mitgegeben werden:
<a href="https://fuhrparkmanagement/me-iq-007&success=http%3A%2F%2Fkundensupport%2Ffall%2F4711"
>Zum Fahrzeug</a>
- UI-Inklusion: Auch eingebundene UI Elemente erlauben eine sehr lose Kopplung zwischen zwei Systemen. Da nur Formate wie pures HTML und CSS ausgetauscht werden, muss das konsumierende System sehr wenig Details über Datenstrukturen oder deren Interpretation von dem Quellsystem wissen. Soll das System Kundendienst beispielsweise Stammdaten über ein Fahrzeug anzeigen, sind nicht die kompletten Datenstrukturen aus dem Fuhrparkmanagement erforderlich, vielmehr reicht eine kurze Beschreibung des Fahrzeugs (siehe Abbildung 4). Die Vorteile liegen auf der Hand: Entscheiden sich Architektinnen oder Architekten beispielsweise, die Daten eines Fahrzeugs anders zu benennen oder weitere Merkmale wie Kennzeichen aufzunehmen, kann das konsumierende System bleiben, wie es ist. Diese sehr lose Form der Kopplung wird jedoch durch eine Abhängigkeit zur Laufzeit bezahlt. Ist das Fuhrparkmanagement-SCS nicht verfügbar, liegen nur noch Stammdaten aus dem Cache vor.
Messaging zum Datenaustausch: Ist Laufzeitabhängigkeit bei der UI-Integration nicht erwünscht oder muss das System die Daten nicht nur anzeigen, sondern auch auswerten, ist eine Integration erforderlich, die zu einer stärkeren Kopplung zwischen beiden Systemen führt. Zu den Grundsätzen der SCS Architektur gehört, dass jedes System über eine eigene Datenbank verfügen soll, die alle Daten enthält, die es benötigt. Verwenden mehrere Systeme bestimmte Daten, wird es nötig, sie zwischen den SCS auszutauschen. Dabei hat sich eine Übertragung in Form von Domain Events bewährt. Dabei erzeugt ein System bei bestimmten Ereignissen (etwa beim Abschluss eines fachlichen Vorgangs) ein Domain Event mit allen relevanten Informationen (aber nicht mehr!) und publiziert es. Das konsumierende System kann auf die Publikation durch eigene Logik reagieren und seine eigene Kopie des entsprechenden Datensatzes anlegen oder anpassen. Bei der redundanten Speicherung ist es wichtig, genau festzulegen, welches System die Wahrheit definiert. Nur aus dieser Quelle sollten nachlaufende Systeme Daten konsumieren oder ändern. Das oben beschriebene Event Storming hat bereits mögliche Events zur Integration identifiziert (siehe die orangen Kreise in Abbildung 1). Es gilt, diese zu relevanten Zeitpunkten aus dem Legacy-System heraus zu publizieren, sodass die neuen SCS sie konsumieren können. Manchmal möchte ein System an anderer Stelle verortete Daten ändern oder anlegen. Am besten lässt sich das durch eine UI-Integration lösen: Muss etwa der Kundendienst einen Eintrag im Fuhrpark korrigieren, ist ein Sprung in die UI des Fuhrparkmanagements mit einem Rücksprung in das Kundendienst-SCS die beste Wahl. Ein Gegenbeispiel, bei dem diese Taktik nicht funktioniert, wäre die automatische Sperrung eines Benutzers, der seine Rechnungen nicht bezahlt. Hier kann ein Command (in Abbildung 1 in grün) das Quellsystem anweisen, eine Änderung der Daten vorzunehmen. Das Quellsystem kann diese Anfrage jedoch ablehnen, wenn sie gegen die eigenen Regeln verstößt.
Integration über synchrone API Aufrufe: Auch über eine RESTful API lässt sich ein anderes System direkt ansprechen. Da diese Methode Systeme zur Laufzeit stark aneinander bindet und zu einer fehleranfälligen, schwer abzufangenden Kaskade an blockierenden Aufrufen führen kann, gilt sie als letzter Ausweg. Sie kommt etwa dann zum Zuge, wenn die Daten aus dem Quellsystem viel zu umfangreich sind, um sie über Events zu replizieren, oder wenn sie nur durch die Anwendung fachlicher Logik quasi ad-hoc generiert werden können.
Fazit
Auch nach der Wahl und Planung eines ersten SCS-Kandidaten bleiben Fragen offen: Wie soll in Zukunft ein gemeinsamer Look and Feel zwischen den Selfcontained Systems und dem Monolithen hergestellt werden? Wie lässt sich eine gemeinsame Nutzer- und Rechteverwaltung etablieren? Diese und noch viele weitere Fragen müssen Architektinnen und Architekten nicht sofort oder zumindest nicht sofort richtig beantworten.
Ein wesentlicher Vorteil der inkrementellen Migrationsstrategie ist, dass die Konsequenz einer Fehlentscheidung nicht katastrophal ist. Solange nur ein oder zwei Systeme laufen, ist Zeit zum Lernen, welche Entscheidungen wirklich für alle Systeme getroffen gelten (Stichwort Makroarchitektur) und welche besser lokal zu fällen sind (Mikroarchitektur). Viele Ansätze zur Integration, Sicherstellung der UI-Konsistenz oder für Single Sign-on hängen von den konkreten Gegebenheiten ab.
Die Makroarchitektur wächst mit jedem weiteren Self-contained System, während das monolithische System weiter schrumpft. Unterstützung in Form von Best Practices für die Makroarchitektur unabhängiger Systeme finden sich auch in den Prinzipien der Independent Systems Architecture, die mit SCS sehr gut harmonieren.
Mit Beharrlichkeit und einem Fokus auf Geschäftswert werden Architektinnen und Architekten eher früher als später eine Landschaft von gut modularisierten, lose gekoppelten SCS und die damit verbundenen Vorteile genießen lernen. Und eventuell werden sie feststellen, dass manche Teile ihres Systems gar keiner Migration bedürfen.
Quellen
-
Sam Newman; Building Microservices: Designing Fine–Grade Systems; 2021 ↩
-
David Parnas; On the Criteria To Be Used in Decomposing Systems into Modules; 1972 ↩
-
Eric Evans; Domain–Driven Design: Tackling Complexity in the Heart of Software, 2003 ↩
-
Sam Newman; Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith; 2019 ↩