Fast jedes Softwaresystem wird mit guten Vorsätzen, aber unter schwierigen Bedingungen entwickelt: Knappe Zeitvorgaben zwingen uns, schnelle Lösungen – Hacks – zu programmieren. Unterschiedliche Qualifikationen im Entwicklungsteam führen zu Code in ebenso unterschiedlicher Güte. Alter Code, den keiner mehr kennt, muss angepasst werden, und vieles mehr. All das führt zu schlechtem verknäultem Code, dem sogenannten Monolithen, der die Entwicklungskosten in Zukunft in die Höhe treibt und den Entwicklungsteams schlaflose Nächte bereitet. Mit unserer Weiterentwicklung des Domain-Driven Designs, der von uns sogenannten Domain-Driven Transformation [1], haben wir eine Methode an der Hand, um solche Monolithen Schritt für Schritt zu zerlegen und wieder in den Bereich der beherrschbaren Wartungskosten zu bringen.
Stay tuned
Immer auf dem Laufenden bleiben! Alle News & Updates:
Der Monolith – ein Big Ball of Mud
Mit dem Begriff „Monolith“ wurden ursprünglich Legacy-Systeme bezeichnet, die als eine einzelne Deployment-Einheit gebaut und ausgeliefert werden müssen – im Gegensatz zur Microservices-Architektur, die aus mehreren Deployment-Einheiten besteht [2], [3] (Abb. 1).
Aus nur einer Deployment-Einheit zu bestehen, ist für viele Systeme kein Problem, sondern sogar die einfachste Lösung. Problematisch wird es allerdings, wenn
-
das System eine gewisse Größe überschreitet (und dadurch Test und Roll-out langsam werden),
-
horizontal skaliert oder
-
mit mehr als einem Team weiterentwickelt werden soll.
Aber ganz besonders problematisch werden Monolithen, wenn sie als stark verwobene chaotische Struktur daherkommen. Auf Klassenebene sieht eine stark verwobene chaotische Struktur zum Beispiel so aus wie in Abbildung 2 dargestellt.
Dieser Klassenzyklus stammt aus einem Java-System mit 650 000 Lines of Code (LOC) und besteht aus 463 Klassen, die sich über 50 Packages verteilen. Jedes Rechteck entspricht einer Klasse und die Pfeile zwischen den Rechtecken zeigen die Beziehungen zwischen den Klassen. Auf Klassenebene ist dieser Zyklus eng verwoben. Das kann man daran erkennen, dass ein Großteil der Klassen sehr eng beieinander liegt und es nur wenige Satelliten gibt. Außerdem sind Klassen aus den gleichen Packages – erkennbar an derselben Farbgebung – über den gesamten Zyklus verteilt, was für wenig Ordnung auch auf den höheren Ebenen spricht.
Seit der Veröffentlichung des gleichnamigen Artikels [4] werden solche verwobenen Strukturen in der DDD-Community als Big Ball of Mud (BBoM) bezeichnet. Im Endeffekt ist ein Big Ball of Mud ein Softwaresystem, bei dem die Menge der Abhängigkeiten, also die Kopplung, nie Thema war und unkontrolliert immer weiter zugenommen hat. Zusammenfassend könnte man sagen: Wer seine Kopplung nicht im Griff hat, hat seine Architektur nicht im Griff.
LUST AUF NOCH MEHR DOMAIN-DRIVEN DESIGN-TRENDS?
Entdecke Workshops vom 25. - 27. November 2024
Typische Architektur eines Big Ball of Mud
Ein Big Ball of Mud hat, wenn man Glück hat, eine Schichtenarchitektur, wie sie Anfang der 2000er Jahre en vogue war (Abb. 3).
Eine solche Schichtenarchitektur führt grundsätzlich dazu, dass der fachliche Sourcecode (Application und Domain) vom technischen Sourcecode (User Interface und Datenbankzugriff) getrennt wird und Zugriffe nur von oben nach unten erlaubt sind. Es entsteht also wenigstens eine technische Ordnung mit Kopplung in einer Richtung im System.
Um die Kopplung noch weiter zu reduzieren, muss man den Big Ball of Mud nach fachlichen Kriterien zerlegen, sodass eine Struktur aus fachlichen Modulen wie in Abbildung 4 entsteht. Jedes Modul wird von einem Team eigenständig bearbeitet und für alle seine Bestandteile, den gesamten Sourcecode, die Datenbank, die Technologien, das Deployment, verantwortlich sein.
Diese Zerlegung weist viele Vorteile auf. Denn neben der Unabhängigkeit der Teams und der modulareren und damit leichter verständlichen Struktur kann das System, wenn man es entlang der fachlichen Module in einzelne Microservices zerlegt, auch skalieren. Wie kann diese fachliche Zerlegung nun vorgenommen werden?
Domain-Driven Transformation
Um eine gute fachliche Zerlegung zu finden, haben wir Domain-Driven Design zu Domain-Driven Transformation weiterentwickelt. Diese Transformation integriert sowohl
-
Methoden zur Erarbeitung der Fachlichkeit mit den Fachexpert:innen als auch
-
Methoden zum Zerlegen von Sourcecode mit Analyse- und Refactoring-Werkzeugen.
Erarbeiten der Fachlichkeit
Wir beginnen die Domain-Driven Transformationen damit, dass wir uns zusammen mit den Fachexpert:innen und Entwickler:innen einen Überblick über die Domäne verschaffen. Das kann entweder mit Event Storming [5] oder mit Domain Storytelling [6] gemacht werden – zwei Methoden, die für Anwender:innen wie Entwickler:innen gleichermaßen gut verständlich sind.
In Abbildung 5 ist eine Domain Story zu sehen, die für ein kleines Programmkino erstellt wurde. In Abbildung 6 sind zwei Bounded Contexts für die Zerlegung eingezeichnet.
Als Personen bzw. Rollen oder Gruppen sind in dieser Domain Story erkennbar: die Werbeagentur, der Kinomanager, der Filmverleiher, der Kassenmitarbeiter und der Kinobesucher. Die einzelnen Rollen tauschen Dokumente und Informationen aus, wie den Buchungsplan der Werbung, die Vorgaben für Filme und die Verfügbarkeit von Filmen. Sie arbeiten aber auch mit „Gegenständen“ aus ihrer Domäne: dem Wochenplan und dem Saalplan. Die Überblicks-Domain-Story beginnt oben mit der Ziffer 1, wo die Werbeagentur dem Kinomanager den Buchungsplan mit der Werbung mitteilt, und endet bei Schritt 11, wenn der Kassenmitarbeiter dem Kinobesucher die Kinokarten übergibt.
An diesem Überblick lassen sich verschiedene Indikatoren erklären, die beim Schneiden einer Domäne helfen:
-
Fertige Arbeitsergebnisse und monodirektionale Weitergabe: Der Wochenplan ist der Arbeitsgegenstand, an dem der Kinomanager bei der Wochenplanung arbeitet. In Schritt 4 gibt er ihn an die andere Subdomain „Kartenverkauf“ weiter. Einen Weg zurück gibt es nicht. Der Wochenplan wird in der Subdomain Kartenverkauf nicht weiterverarbeitet, sondern bildet die Grundlage der Arbeit in dieser Subdomain.
-
Gruppen von Fachexperten: Bei unserem kleinen Programmkino haben wir zwei Fachexperten, nämlich den Kassenmitarbeiter und den Kinomanager, jeweils als Einzelperson. Im Kinokonzern aber gibt es eine ganze Menge von Kassenmitarbeitern und pro Kino einen eigenen Kinomanager. Es gibt also Gruppen von Fachexperten, die ihren jeweiligen Teilprozess sehr genau beherrschen, aber den oder die anderen Teilprozesse nur grob kennen.
-
Definition von Fachbegriffen und Handhabung von Arbeitsgegenständen: Der Wochenplan ist auch ein gutes Beispiel für die unterschiedliche Bedeutung von Fachbegriffen und dem, was man mit ihnen tut, als Hinweis auf Subdomains. In der Subdomain „Wochenplanung“ ist der Wochenplan das zentrale Arbeitsmittel und enthält neben den geplanten Vorstellungen noch viele andere Informationen, wie die vor dem Film geplante Werbung, den Einsatz der Reinigungskräfte und Eisverkäufer und sicherlich auch die erwarteten Besucherzahlen. Der Kinomanager hat also einen reichhaltigen Umgang mit dem Wochenplan. In der Subdomain „Kartenverkauf“ ist der Wochenplan auch wichtig, aber er enthält nur die Vorstellungen. Würde der Wochenplan hier auch die Werbeblöcke zeigen, würden die Kinobesucher sicherlich erst zum Beginn des Films kommen und so die Werbeeinnahmen des Kinos schmälern. In der Subdomain „Kartenverkauf“ haben wir also nur einen vereinfachten Wochenplan, der den Kinobesuchern als Informationsmedium dient, viel weniger Handhabung erlaubt und für den Kassenmitarbeiter und die Kinobesucher eine ganz andere Bedeutung hat als für den Kinomanager.
-
Rhythmen und Auslöser: Im Kino gibt es unterschiedliche Rhythmen und Auslöser: Die Wochenplanung wird einmal pro Kinowoche vom Kinomanager durchgeführt, während der Teilprozess des Kartenverkaufs immer dann durchlaufen wird, wenn ein Kunde kommt. Die jeweiligen Auslöser sind bei der Subdomain „Wochenplanung“ der Schritt 1 in Abbildung 6, wenn die Werbeagentur den Buchungsplan für die Werbung schickt. Beim Kartenverkauf ist der Auslöser Schritt 6, wenn der Kinobesucher ins Kino kommt und den Wochenplan studiert.
Für echte große Anwendungen in Unternehmen sind die Überblicks-Domain-Stories in der Regel deutlich größer und umfassen mehr Schritte. Sogar bei unserem kleinen Programmkino fehlen im Überblick die Eisverkäufer und das Reinigungspersonal, die sicherlich auch mit der Software interagieren werden. Die Indikatoren, nach denen man in seiner Überblicks-Domain-Story suchen muss, gelten allerdings sowohl für kleine als auch für größere Domänen.
Zerlegen des monolithischen Sourcecodes
Mit der fachlichen Aufteilung in Subdomänen im Rücken können wir uns nun wieder dem Monolithen und seinen Strukturen zuwenden. Bei dieser Zerlegung setzen wir Architekturanalysetools ein [7], die es uns erlauben, die Architektur im Tool umzubauen und Refactorings zu definieren, die für den echten Umbau des Sourcecodes notwendig sind. Hier eignen sich unterschiedliche Tools: der Sotograph, der Sonargraph, Structure 101, Lattix, Teamscale, Axivion Bauhaus Suite und bestimmt noch weitere, die wir nicht kennen.
In Abbildung 7 ist zu sehen, wie die Zerlegung des Monolithen mit einem Analysetool durchgeführt wird. Die Analyse wird von einem Toolpilot, der sich mit dem jeweiligen Tool und der bzw. den eingesetzten Programmiersprachen auskennt, gemeinsam mit allen Architekt:innen und Entwickler:innen des Systems in einem Workshop durchgeführt. Zu Beginn des Workshops wird der Sourcecode des Systems mit dem Analysewerkzeug geparst (1), und die vorhandenen Strukturen werden erfasst (z. B. BuildUnits, Eclipse-/VisualStudio-Projekte, Maven-Module, Package-/Namespace-/Directory-Bäume, Klassen). Auf diese vorhandenen Strukturen werden nun fachliche Module modelliert (2), die der fachlichen Zerlegung entsprechen, die mit den Fachexpert:innen entwickelt wurde. Dabei kann das ganze Team sehen, wo die aktuelle Struktur nahe an der fachlichen Zerlegung ist und wo es deutliche Abweichungen gibt. Nun macht sich der Toolpilot gemeinsam mit dem Entwicklungsteam auf die Suche nach einfachen Lösungen, wie die vorhandene Struktur durch Refactorings an die fachliche Zerlegung angeglichen werden kann (3). Diese Refactorings werden gesammelt und priorisiert (4). Manchmal stellen der Toolpilot und das Entwicklungsteam in der Diskussion fest, dass die im Sourcecode gewählte Lösung besser oder weitergehend ist als die fachliche Zerlegung aus den Workshops mit den Anwender:innen. Manchmal ist aber weder die vorhandene Struktur noch die gewünschte fachliche Zerlegung die beste Lösung und beides muss noch einmal grundsätzlich überdacht werden.
Schön wäre es, wenn eine Zerlegung möglich wäre, wie sie in Abbildung 8 links dargestellt ist. Auf der linken Seite der Abbildung sieht man die Bounded Contexts aus dem Kinobeispiel aus Abbildung 6. Auf der linken Seite sind zwei unabhängige fachliche Module zu erkennen, die sich gegenseitig lediglich durch asynchrone Aufrufe über Änderungen informieren. Ansonsten können die beiden Bounded Contexts ihre Arbeit unabhängig voneinander erledigen. Rechts hingegen sind die User Interfaces außerhalb der Entity-orientierten Bounded Contexts untergebracht, damit sie synchrone Aufrufe an den jeweiligen Entity-orientierten Service machen können. Die Konstruktion auf der rechten Seite ist nicht zu empfehlen, weil sie den Big Ball of Mud nicht auseinandernimmt. Sie entspricht auch nicht der von DDD empfohlenen Umsetzung und ist hier nur zur Vermeidung von Missverständnissen dargestellt.
Das übergreifende Domänenmodell
Den größten Knackpunkt bei der Zerlegung stellt in den meisten Monolithen das übergreifende Domänenmodell dar. Wenn wir uns große Monolithen anschauen, dann finden wir dort in der Regel ein Domänenmodell, das von allen darauf aufbauenden Teilen der Software verwendet wird. Gut illustrieren lässt sich das an einem System, dass wir vor einiger Zeit untersuchen durften. Die Architekten waren mit dem Wunsch an uns herangetreten, eine Million Zeilen Java-Sourcecode in Microservices zu zerlegen. Auf die Frage, was für eine Architektur das System habe, wurde uns keine Schichtenarchitektur, sondern eine Architektur entlang von Use Cases avisiert. Voller Hoffnung machten wir uns mit den Architekten an die Arbeit, die Architektur mit dem von uns häufig eingesetzten Tool Sotograph [8] zu modellieren und den Sourcecode den modellierten Elementen zuzuordnen.
In Abbildung 9 sieht man dieses System auf der linken Seite mit seiner ersten Aufteilung in Use Cases. Jedes Rechteck „mailing“, „importexport“ usw. beinhaltet einen oder mehrere Package-Bäume mit den darin enthaltenen Klassen. Die Klassen aus den verschiedenen Packages haben Beziehungen zueinander, um ihre jeweiligen Aufgaben zu erfüllen. Diese Beziehungen werden im Sotographen durch die grünen und roten Bögen dargestellt. Die grünen Bögen stellen Beziehungen von oben nach unten dar, die roten Bögen von unten nach oben.
Nachdem wir mit den Architekten die Use Cases als Architekturelemente (Rechteck) modelliert hatten, blieb ein großer Teil des Package-Baums übrig, der den Namen „model“ trug. In Abbildung 9 haben wir links ein eigenes Architekturelement für „model“ hinzugefügt. In „model“ befinden sich alle Domänenmodellklassen gesammelt an einer Stelle. Das ist erst einmal kein Problem – es wird nur dann zu einem, wenn diese Modellklassen von überallher verwendet werden. Um diesen Umstand zu überprüfen, haben wir versucht, die Modellklassen aus „model“ den einzelnen Use Cases zuzuordnen, indem wir die Klassen von unten nach oben verschoben haben. Auf der rechten Seite von Abbildung 9 sieht man das vorläufige Ergebnis, als wir bis zu „calculation“ gekommen waren.
Rechts in der Abbildung sieht man, wie stark das Domänenmodell von überallher verwendet wird und wie stark sich die Domänenmodellklassen untereinander verwenden. In DDD würde man sagen: Das ist ein Big Ball of Mud. Was ist passiert?
Jede:r Entwickler:in, die bzw. der neue Funktionalität in das System eingebaut hat, hat dafür die zentralen Klassen des Domänenmodells gebraucht. Allerdings musste er bzw. sie diese Klassen erweitern, damit sich die neue Funktionalität mit den Domänenmodellklassen umsetzen lässt. So bekamen die zentralen Klassen mit jeder neuen Funktionalität ein bis zwei neue Methoden und Attribute hinzu. Aus dem Blickwinkel der Wiederverwendung von vorhandenen Klassen ist das eine logische Konsequenz. Das Ärgerliche ist nur, dass die Architektur so auf der Ebene des Domänenmodells sehr stark verkoppelt wird.
Domain-Driven Design geht an dieser Stelle den entgegengesetzten Weg. Der Monolith soll in fachliche Module (Bounded Contexts) aufgeteilt werden, und in jedem Bounded Context existieren die Klassen des Domänenmodells, die dort benötigt werden. Das bedeutet, dass die Kernklassen des Domänenmodells durchaus mehrfach im System vorkommen werden. Zugeschnitten auf ihren jeweiligen Bounded Context bieten sie die Methoden an, die dort benötigt werden, und nicht mehr.
Will man einen Monolithen fachlich zerlegen, so muss man das übergreifende Domänenmodell zerschlagen. Das ist in den meisten großen Monolithen eine Herkulesaufgabe. Zu verwoben sind die auf dem Domänenmodell aufsetzenden Teile des Systems mit den Klassen des Domänenmodells. Um hier weiterzukommen, kopieren wir zuerst die Domänenklassen in jeden Bounded Context, der sie braucht (Abb. 10). Wir duplizieren also Code und bauen diese Domänenklassen dann jeweils für ihren Bounded Context zurück. So bekommen wir kontextspezifische Domänenklassen, die von ihrem jeweiligen Team unabhängig vom Rest des Systems erweitert und angepasst werden können.
Selbstverständlich müssen bestimmte Eigenschaften der Domänenmodellklassen, wie z. B. die ID und der Name, in allen Bounded Contexts gleich gehalten werden, in denen eine Variante einer Domänenklasse vorkommt. Außerdem müssen neue Objekte einer Domänenklasse in allen Bounded Contexts bekannt gemacht werden, wenn in einem Bounded Context ein neues Objekt angelegt wird. Diese Informationen werden über Updates von einem Bounded Context an alle anderen Bounded Contexts gemeldet, die mit dem jeweiligen Objekt arbeiten.
Fazit
Monolithen sind in den meisten Unternehmen das Ergebnis von zehn bis fünfzehn Jahren Programmierung. Dabei hat meistens die Zeit für Refactoring und Überarbeitung der Architektur gefehlt. Um im eigenen Unternehmen einen Monolithen zu zerlegen, muss zuerst die fachliche Domäne in Subdomänen zerlegt werden und diese Struktur im Anschluss auf den Sourcecode übertragen werden. Dieses Vorgehen nennen wir Domain-Driven Transformation und beschreiben es in unserem gleichnamigen Buch [1].
Links & Literatur
[1] Lilienthal, Carola; Schwentner, Henning: „Domain-Driven Transformation: Monolithen und Microservices zukunftsfähig machen“, Heidelberg, dpunkt.verlag, 2023
[2] Newman, Sam: „Building Microservices: Designing Fine-Grained Systems“, 2. Aufl., Sebastopol, CA, O’Reilly, 2022
[3] Wolff, Eberhard: „Microservices: Grundlagen flexibler Softwarearchitekturen“, 2. Aufl., Heidelberg, dpunkt.verlag, 2018.
[4] Foote, Brian; Yoder, Joseph: „Big Ball of Mud“. PLoP ’97, Monticello, IL, September 1997; http://www.laputan.org/mud/mud.html
[5] Brandolini, Alberto: „Introducing EventStorming“, Selbstverlag, Leanpub, 2021; https://leanpub.com/introducing_eventstorming
[6] Hofer, Stefan; Schwentner, Henning: „Domain Storytelling: a Collaborative, Visual, and Agile Way to Develop Domain-Driven Software“, Boston, Addison-Wesley, 2022
[7] Lilienthal, Carola: „Langlebige Softwarearchitekturen: Technische Schulden analysieren, begrenzen und abbauen“, 3. Aufl., Heidelberg, dpunkt.verlag, 2019