Microservices sind in aller Munde. Mit diesem Architekturstil werden uns bessere Wartbarkeit, kürzere Time to Market, einfachere Skalierbarkeit und alle sonst noch denkbaren Verbesserungen für die Softwareentwicklung versprochen. Zunächst wurden Microservices vor allem als technisches Thema verstanden. Um ein modernes verteiltes System zu bauen, braucht es schließlich die Beherrschung verschiedenster Technologien. Schnell wurde allerdings klar, dass für eine tragfähige Microservices-Architektur ein guter Schnitt nötig ist und dass ein guter Schnitt nur einer sein kann, der auf der Fachlichkeit basiert. Dadurch wurde ein Thema ins Rampenlicht geholt, das vorher (zu Unrecht) nur einer kleinen Gruppe von Experten bekannt war – Domain-driven Design oder kurz DDD.
„Fokussiere Dich auf die Fachlichkeit deiner Software, verstehe zunächst das Problem, bevor du mit einer Lösung durch die Tür kommst.“ Das ist die grundlegende Lehre von DDD. Um die Fachlichkeit zu lernen, brauchen wir Kommunikation mit den Fachexperten. Schauen wir uns an einem Beispiel an, was das konkret bedeutet.
Beispieldomäne
Unser Beispiel liegt in der Domäne „Bankwesen“: Ein Kunde geht zur Bank und eröffnet ein Girokonto. Dann zahlt er einen Betrag von 100 € ein und überweist diesen auf das Konto eines anderen Kunden. Am Ende des Jahres berechnet ein Bankmitarbeiter den Jahreszins für das Konto und zahlt ihn auf das Konto ein.
Die Geschichte der Domäne stellt man gerne in einer sogenannten Domain Story (Kasten: „Domain Storytelling“) dar. Hierbei wird das, was in der Domäne passiert, in einem Diagramm dargestellt. Um die Domain Story zu lesen, gilt es, den Sequenznummern zu folgen (Abb. 1).
Domain Storytelling
Domain Stories sind eine einfache grafische Notation und stammen aus dem Domain Storytelling [1], [2]. Es gibt zwei Arten von Icons und eine Art von Pfeil:
-
Actor – die handelnden Personen
-
Work Object – die Arbeitsgegenstände, mit denen etwas getan wird
-
Activity – die Handlungen, die die Akteure an den Arbeitsgegenständen vornehmen
Domain Stories entstehen, indem wir uns von den Fachexperten erzählen lassen, was in der Domäne geschieht. Während der Erzählung wird die Geschichte so, wie wir sie verstehen, aufgezeichnet. So geben wir direkte Rückmeldung über das Verständnis, und mögliche Missverständnisse werden schnell aufgeklärt.
LUST AUF NOCH MEHR DOMAIN-DRIVEN DESIGN-TRENDS?
Entdecke Workshops vom 25. - 27. November 2024
Lösungsversuch
Jeder von uns kennt diese Geschichte, aber wie baut man eine Software dafür? Wie würde das typische Entwicklungsteam an diese Aufgabe herangehen? Das Team aus Technikern beantwortet zuerst die (scheinbar) wichtigsten Fragen: Welche Programmiersprache? Wie schaffen wir es, die neuesten Frameworks/Versionen zu verwenden? Können wir statt einer klassischen relationalen DB modernes NoSQL einsetzen?
Moment mal! Haben diese Fragen irgendetwas mit der Bank und Konten zu tun? Hat uns ihre Beantwortung näher an unser Ziel gebracht? Fühlt sich nicht so an. Was ist denn überhaupt unser Ziel?
Leider ist das Ziel nicht technische Schönheit. Das Ziel ist es, den Anwenderinnen und Anwendern in ihrer Domäne zu helfen; sie bei ihrer Arbeit zu unterstützen; diese Arbeit leichter, schneller, effizienter zu machen. Wenn wir das mit Software schaffen – super! Deshalb sagt Domain-driven Design: Baue die Software so, dass sie in der Domäne tief verwurzelt ist. Baue sie als Reflektion der Domäne. Damit wir die Domäne in Software reflektieren können, müssen wir sie verstehen. Mit diesem Verständnis entwickeln wir ein Modell und implementieren es – das sogenannte Domänenmodell.
Ubiquitous Language
Damit die Entwickler das Domänenmodell bauen können, müssen sie mit den Fachexperten über die Domäne sprechen können – deshalb brauchen wir eine gemeinsame Sprache für Entwickler und Fachexperten. Die Entwickler haben eine technische Sprache. Sie reden über Klassen, Methoden und Interfaces. Auch für sie spielen Konten eine Rolle, aber wenn wir sagen: „Ich logge mich in mein Konto ein“, geht es uns nicht um das Girokonto aus der Bankgeschichte. Diese technische Sprache ist wichtig, allerdings nicht besonders gut geeignet, um über die Domäne zu kommunizieren. Die Domänenexperten haben ihre eigene Fachsprache. In der Bank sprechen wir z. B. über Girokonto, Geldbetrag, Währung, einzahlen, überweisen usw.
DDD sagt, dass wir eine gemeinsame Sprache für Fachexperten und Entwickler haben wollen, um Sprachverwirrung zu vermeiden. Sie soll auf der Fachsprache basieren und überall verwendet werden. Überall bedeutet im Gesprochenen, im Geschriebenen, in Diagrammen und eben auch im Code. Diese Sprache wird deshalb Ubiquitous Language (also allgegenwärtige Sprache) genannt. Wenn wir die Domain Story aus unserem Beispiel anschauen, sehen wir ausschließlich Fachwörter und keine technischen Wörter. Das ist gut so, wir haben uns also wirklich auf die Fachlichkeit fokussiert. Alle diese Wörter sind Kandidaten dafür, auch Wörter der Ubiquitous Language zu werden.
DDD im Kleinen
DDD gibt uns Konstruktionsanleitungen für Domänenmodelle und zwar im Großen und Kleinen. Im Kleinen betrachten wir, welche Arbeitsgegenstände unsere Fachexperten verwenden und, noch wichtiger, was sie damit tun, wie sie damit umgehen. Auch hier lassen wir uns also von der Domäne treiben. Aus den Gegenständen werden Klassen. Aus den Umgangsformen werden Methoden.
Was heißt das in unserem Beispiel? Ein zentraler Gegenstand im Bankwesen ist das Girokonto. Also implementieren wir eine entsprechende Klasse Girokonto in unser Domänenmodell. Was wird mit einem Girokonto getan? Beispielsweise wird ein Betrag eingezahlt und Jahreszinsen werden berechnet. Deshalb bekommt die Klasse Girokonto entsprechende Methoden. In Abbildung 2 sieht man, wie aus einer Domain Story das Domänenmodell abgeleitet werden kann. Als zusätzliche Klasse kommt noch Betrag dazu. In Java würde daraus folgender Code entstehen:
public class Girokonto {
public void zahleEin(Betrag betrag)
//...
}
Darin steckt eine ganze Menge Ubiquitous Language: Die Wörter „Girokonto“, „einzahlen“, „Betrag“ stammen alle aus der Fachsprache. Es ist ein ausdrucksstarkes Domänenmodell mit reichem fachlichem Verhalten („domain model with rich behaviour“). Nicht nur die Klasse, auch die Methoden haben einen fachlichen Namen.
In der Praxis trifft man oft auf das Gegenteil, das sogenannte Anemic Domain Model. Dieses hat zwar fachliche Klassen, aber diese haben kein fachliches Verhalten. Bei dem Girokonto würde man aus technischer Sicht z. B. denken, dass alles, was wir damit machen können, am Ende ja immer den Kontostand ändert. Also ist eine mögliche anämische Implementierung die folgende:
public class Girokonto {
public void setKontostand(Betrag kontostand)
//...
}
Man erkennt anämische Domänenmodelle daran, dass ihre fachlichen Klassen hauptsächlich aus Gettern und Settern bestehen. Hier ist jeder fachliche Umgang verloren. Das Modell ist nicht mehr so ausdrucksmächtig. Außerdem kann so nicht mehr sichergestellt werden, dass nur fachlich sinnvolle und valide Operationen ausgeführt werden. „Kontostand setzen“ ist nicht das, was man mit einem Girokonto machen kann – Konten bekommen nicht auf einmal einen beliebigen Kontostand. Das wird noch klarer sichtbar, wenn wir uns das Abheben anschauen. Das ist in der Fachlichkeit nur möglich, wenn das Konto gedeckt ist. Im anämischen Modell wird die Deckung nicht geprüft und das Konto kann illegal überzogen werden. Im sauberen Domänenmodell wird auch sauber geprüft und Überziehen so unmöglich gemacht (Listing 1).
Listing 1
public class Girokonto {
public void zahleEin(Betrag betrag)
// ...
public boolean isGedecktMit(Betrag betrag)
// ...
public void hebeAb(Betrag betrag) {
if (!isGedecktMit(betrag)) {
throw new Exception("Vorbedingung verletzt");
}
// ...
}
}
So können die Klassen des Domänenmodells sicherstellen, dass ihre Instanzen immer einen validen Zustand haben. Das Entwerfen des Domänenmodells nennt DDD auch taktisches Design. Weitere Informationen dazu findet man z. B. unter [3].
Ein reiches Domänenmodell ist etwas Großartiges. Problematisch ist, dass es immer weiterwächst. In unserem Beispiel kommen für die Klasse Girokonto noch die Methoden berechneJahreszins() und ueberweise() hinzu, und wir haben ja bisher nur ein kleines Stück der Domäne modelliert. Irgendwann wird das Modell so groß sein, dass es nicht mehr im Ganzen zu verstehen und deshalb fehleranfällig ist. DDD im Kleinen mit taktischem Design reicht also nicht aus, wir brauchen auch noch strategisches Design – DDD im Großen.
DDD im Großen – die Domäne aufteilen
Je kleiner ein Modell ist, desto einfacher kann es verstanden werden. Das strategische Design von DDD sagt uns deshalb, dass wir nicht nur ein, sondern mehrere Domänenmodelle bauen sollen. Jedes dieser Modelle existiert in einem eigenen Kontext und ist klar von den anderen Modellen abgegrenzt (bounded). Deshalb spricht man hier von Bounded Context. Das einzelne Modell bildet dann nur diejenigen Eigenschaften des Realweltgegenstands ab, die im jeweiligen Kontext wesentlich sind. Die Domäne wird so in unterschiedliche Bounded Contexts aufgeteilt. Dabei suchen wir nach Bereichen, in denen Unterschiedliches getan wird (Abb. 3).
Es haben sich also die drei Bounded Contexts Kontoeröffnung, Kontoführung und Zinsberechnung herauskristallisiert. Mit diesen Kontexten kann man eine sogenannte Context Map zeichnen. In ihr finden sich die Kontexte und die Beziehungen dazwischen wieder. Jeder Kontext ist dann auch Kandidat für einen Microservice (Abb. 4).
Ubiquitous Language und Modell hängen eng zusammen. Bei genauem Hinsehen erkennt man, dass jedes Modell (d. h. jeder Bounded Context) sogar seine eigene Ubiquitous Language hat. Oben haben wir gesagt, dass wir mehrere unterschiedliche Domänenmodelle bilden. Das heißt, wir bilden denselben Gegenstand aus der Wirklichkeit mit unterschiedlichen Klassen ab. Unterschiedliche Klassen mit demselben Namen. Daran muss man sich erst einmal gewöhnen! Unsere Beispielmodelle sehen dann wie in Abbildung 5 aus.
Bounded Contexts müssen nicht gleich als Microservices implementiert werden. In Java kann man die unterschiedlichen Kontexte auch mit Packages oder (ab Version 9) mit Modules ausdrücken. Hier hätte man dann also zwei Packages oder Module de.bank.kontofuehrung und de.bank.zinsberechnung, die jeweils eine Klasse Girokonto enthalten.
Erste Schritte
Wie führe ich DDD nun in meinem eigenen Projekt ein? Auf der grünen Wiese ist das natürlich leichter als in einem bestehenden Projekt, das vielleicht eine über viele Jahre gewachsene Legacy-Anwendung betreut. Aber auch im zweiten Fall ist es möglich, die Vorteile zu realisieren. In einem neuen Projekt empfiehlt es sich, zunächst grob die Domäne im Ganzen zu modellieren und daraus den Domänenschnitt abzuleiten. Hierzu eignet sich Big Picture Event Storming oder Coarse-grained Domain Storytelling. Als Ergebnis bilden wir eine Context Map.
Als Nächstes schauen wir nun in den jeweiligen Bounded Context hinein und modellieren, was dort im Detail vor sich geht. Hieraus leiten wir das Domänenmodell für diesen Kontext ab. Das Domänenmodell wird in der angemessenen Technologie und Programmiersprache implementiert. Auch hier bieten sich wieder Event Storming und Domain Storytelling an; nun aber in den Varianten Detaillevel bzw. Fine-grained.
Um im Brownfield DDD einzuführen, ist es sinnvoll, in mehreren Schritten vorzugehen. Das System wird nicht von einem Tag auf den anderen durch Wunderheilung zu einem Ideal.
-
Domänenmodellierung und Domänenschnitt, wie wenn man sich auf der grünen Wiese befände, d. h., Entwicklung der Soll-Architektur
-
Analyse der Ist-Architektur; das kann händisch oder toolgestützt erfolgen
-
Übereinanderlegen von Ist- und Soll-Architektur: An welchen Stellen kann das Altsystem in Richtung der gewünschten neuen Struktur bewegt werden?
-
Herauslösen eines ersten Bounded Context aus dem Big Ball of Mud (d. h. dem komplex verwobenem Softwaremonolithen); beim ersten Mal bietet es sich an, einen weniger wichtigen auszuwählen, also eine Supporting oder Generic Subdomain – hieran wollen wir lernen
-
Herauslösen der (ersten) Core Domain – hiermit wollen wir jetzt wirklich Nutzen erzielen
Im Text ist dieses Vorgehen schnell beschrieben, im richtigen Leben kann es natürlich komplex werden. Trotzdem lohnt es sich in den meisten Fällen, diesen Weg zu gehen. Das liegt daran, dass Altsysteme, also unsere Big Balls of Mud, typischerweise too big to fail sind. Sie sind so groß, dass ein kompletter Neubau Jahre dauern würde. Diese Zeit ist zu lang, als dass man das Altsystem im gleichen Zustand belassen könnte. Stattdessen entwickelt man es weiter. Das Neusystem kommt also gar nicht hinterher. Deshalb empfiehlt sich das hier beschriebene kleinschrittige Vorgehen, bei dem das Altsystem nicht durch ein Neusystem ersetzt wird, sondern sich das Altsystem in das Neusystem verwandelt und weiterentwickelt.
Das kann auch bei Technologie- und sogar bei Programmiersprachenwechseln funktionieren. So werden auch alte Mainframesysteme, die in COBOL implementiert sind, in eine modernere Programmiersprache migriert und in die Cloud verschoben. Auch dabei wird schrittweise, d. h. Kontext für Kontext, migriert. Wichtig dabei: Das Ziel ist nicht unbedingt die vollständige Migration, sondern das Erhalten (bzw. Wiederherstellen) der Zukunftsfähigkeit. Meist entsteht dann ein Mischsystem, in dem einzelne Bounded Contexts in der neuen Welt, andere in der alten Welt implementiert sind. Evolution kann eben nur schrittweise stattfinden, wobei jeder Schritt eine lebensfähige Version des Systems ist.
Fazit und Ausblick
Domain-driven Design ist eine großartiges Denk- und Entwurfswerkzeugkiste, um Programmiererinnen und alle anderen, die an Softwareentwicklung arbeiten, auf das zu fokussieren, was wirklich wichtig ist: die Fachlichkeit. Wichtige Werkzeuge sind Strategic Design, Ubiquitous Language und Tactical Design. Das hilft uns insbesondere in der modernen Welt, in der unsere Systeme schnell anpassbar, Cloud-fähig und idealerweise als Microservices implementiert sein sollen.
In einem Überblicksartikel wie diesem kann man das Thema nur an der Oberfläche ankratzen. Wer sich tiefer einlesen will, aber nicht viel Zeit hat, dem sei „Domain-Driven Design kompakt“ [4] von Vaughn Vernon empfohlen. Wer sehen will, wie DDD in der Praxis umgesetzt wird, findet mit dem LeasingNinja [5] ein konkretes Beispiel. Anhand der Domäne Leasing wird hier exemplarisch gezeigt, wie man von der Fachlichkeit über Domain Stories, Context Map und taktisches Design bis in die Implementierung in Code kommt.
Links & Literatur
[1] Hofer; Stefan; Schwentner, Henning: „Domain Storytelling – Collaborative Modeling for Agile and DDD“: https://leanpub.com/domainstorytelling
[2] https://www.domainstorytelling.org
[3] Schwentner, Henning: „Taktisches Design“; in: Entwickler Magazin Spezial Vol. 24
[4] Vernon, Vaughn: „Domain-Driven Design kompakt“, dpunkt 2016