TL;DR

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.

Kasten 1: Kleine Bausteine, lose verbunden

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.

Flussdiagramm eines Fahrzeugverwaltungssystems mit Prozessen wie Nutzerregistrierung, Fahrzeugverfügbarkeit, Trips, Abrechnung und Kundendienst. Legende erklärt blaue Boxen als Prozessbeschreibungen, gelbe Ovale als Domain Events und grüne Kommandos wie 'Nutzer sperren?'.
Abbildung 1: Das Ergebnis der fachlichen Analyse dient dazu, sinnvolle Schnittstellen für den Legacy-Monolith zu finden.

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).

Flow diagram of the Strangler Facade: user requests pass through the facade to Legacy System (complete functionality) and Strangler Facade (extracted functions). Each system uses separate databases ('Legacy System DB,' 'Strangler App DB') with data sync between them. A legend explains symbols for systems, databases, and usage.
Abbildung 2: Die Strangler Fig Application übernimmt nach und nach immer mehr Funktionen des Legacy-Systems. Nutzerinnen und Nutzer sehen immer nur die Fassade.

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.

Zweidimensionales Matrixdiagramm mit den Achsen 'Einfach zu integrieren' und 'Erwarteter Geschäftswert'. Hervorgehoben ist 'Abrechnung' als 'Gute Kandidaten'.
Abbildung 3: Eine Entscheidungsmatrix hilft dabei, das erste Self-contained System zu identifizieren.

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:

<a href="https://fuhrparkmanagement/me-iq-007&success=http%3A%2F%2Fkundensupport%2Ffall%2F4711"
>Zum Fahrzeug</a>
Diagram showing interaction between a Car Sharing (Legacy System) and Fuhrparkmanagement, with data on an Audi A6 (2017, weiß) retrieved via HTTP GET request.
Abbildung 4: Ein UI-Element wird über HTTP von einem SCS geladen.

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

  1. Sam Newman; Building Microservices: Designing Fine–Grade Systems; 2021  ↩

  2. David Parnas; On the Criteria To Be Used in Decomposing Systems into Modules; 1972  ↩

  3. Eric Evans; Domain–Driven Design: Tackling Complexity in the Heart of Software, 2003  ↩

  4. Sam Newman; Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith; 2019  ↩