Start / Seminare / Domain-Driven Design in der Praxis
Modul
Aggregate im Bounded Context
6 Kapitel in diesem Modul-Video · Laufzeit
Transkript
Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.
DDD — Aggregate im Bounded Context
0:00 Bisher hatten wir einzelne Klassen — eine Entity hier, ein Value Object dort. In diesem Modul bekommt das Modell zum ersten Mal eine Struktur, die darüber hinausgeht: das Aggregat. Es beantwortet eine Frage, die in jedem größeren Projekt irgendwann auftaucht — welche Objekte gehören so eng zusammen, dass man sie nur gemeinsam laden, prüfen und speichern darf?
0:21 Wir ziehen diese Grenzen am Kartenautomaten, der ab jetzt tatsächlich etwas verkauft. Und am Ende des Moduls werden wir auf eine Fachregel stoßen, die niemand angefragt hat und die das Modell nicht komplizierter, sondern einfacher macht. Solche Momente sind der eigentliche Ertrag von DDD.
Aggregate im Bounded Context
0:39 Der Weg durch dieses Modul führt vom Konkreten ins Abstrakte und wieder zurück. Erst die neuen Anforderungen an den Verkauf, dann die Frage, was ein Aggregat überhaupt ausmacht und wo seine Grenzen verlaufen. Danach die Basisklasse für die Wurzel, das Nachziehen der offenen Regeln — und zum Schluss jene verborgene Anforderung, die erst sichtbar wird, wenn man beim Rückgeld genau hinschaut.
1:00 Achten Sie darauf, wie sich das Modell dabei verändert: nicht durch bessere Technik, sondern durch bessere Fragen an den Fachbereich.
Anforderungen an den Verkauf
1:08 Fangen wir mit dem an, was unser Automat noch nicht kann. Er nimmt Geld an, er gibt es zurück — verkaufen kann er nichts. Das ändern wir jetzt, und dabei kommen vier Regeln ins Spiel, die harmloser aussehen, als sie sind. Denn der Reiz liegt nicht im Verkaufen selbst, sondern darin, dass die neuen Regeln mehrere Objekte gleichzeitig betreffen — und genau da hört die Einzelklasse auf zu tragen.
1:31 Der Automat des Waschsalons verkauft Wertkarten aus drei Fächern, jedes Fach hält gleichartige Karten zu einem Preis. Stellen Sie sich einen ganz normalen Warenautomaten vor: drei Schächte, in jedem ein Stapel identischer Karten, an jedem ein Preisschild. Bisher fehlt beides — Ware und Rückgeld. Und das ist mehr als eine Lücke im Funktionsumfang. Mit dem Verkauf kommen Regeln ins Modell, die mehrere Objekte gleichzeitig betreffen: Geld, Fach, Bestand.
1:59 Genau an dieser Stelle reichen einzelne Klassen nicht mehr aus, und das Aggregat wird notwendig. Vier Regeln, und sie werden von oben nach unten anspruchsvoller. Die erste ist banal: Das Geld muss reichen. Die zweite auch: Ein leeres Fach kann nichts verkaufen — aber beachten Sie den Zusatz, es muss das melden. Die dritte verlangt Rückgeld.
2:20 Und die vierte ist die, die alles ändert: Reicht das Wechselgeld im Automaten nicht, kommt der Kauf gar nicht erst zustande. Das ist keine Fehlermeldung im Nachhinein, sondern eine Bedingung vorher. Damit hängt der Verkauf plötzlich vom Bestand des Automaten ab — und aus vier unabhängigen Regeln wird ein Geflecht, das nur als Ganzes stimmen kann.
2:41 Der Kartenstapel ist ein Value Object — nach den Prüffragen aus Modul 2 eine klare Sache: Zwei Stapel mit derselben Karte, derselben Anzahl und demselben Preis sind austauschbar. Er trägt drei Angaben und eine Invariante, und die Invariante steht wieder im Konstruktor: Eine negative Anzahl kann gar nicht erst entstehen.
2:59 Wichtig ist die Fußzeile, weil sie die Konsequenz aus der Unveränderlichkeit zieht. Wird eine Karte entnommen, verändert sich der Stapel nicht — es entsteht ein neuer mit einer Karte weniger. Das fühlt sich zunächst umständlich an und erspart Ihnen später eine ganze Klasse von Fehlern. Der erste Punkt ist eine Modellierungsfrage mit Folgen: Der Preis gehört ans Fach, nicht an den Automaten — sonst können nicht drei Fächer drei Preise haben, und Sie merken es erst, wenn der Fachbereich genau das verlangt.
3:28 Der zweite Punkt ist der Rückfall ins veränderliche Denken: einfach den Zähler herunterzählen und damit an der Invariante vorbei. Der dritte ist eine Arbeitsweise-Warnung, die Sie gleich im Vorgehen wiederfinden — eine Regel nach der anderen. Und der vierte ist die Vorwarnung auf das letzte Kapitel: Das Rückgeld ist kein Nachgedanke, es verändert das Modell am stärksten von allen vier Regeln.
Was ein Aggregat ausmacht
3:51 Damit haben wir ein Geflecht aus Objekten, das nur gemeinsam gültig sein kann. Genau dafür gibt es einen Namen und ein Muster. Schauen wir uns an, was ein Aggregat ausmacht und warum es immer genau eine Tür nach außen gibt. Und gleich hinterher die Frage, was eigentlich passiert, wenn jemand an dieser Tür vorbeigeht. Ein Aggregat fasst Entities zu einem begrifflichen Ganzen zusammen und stellt sicher, dass dieses Ganze jederzeit gültig ist.
4:17 Denken Sie an eine Bestellung mit ihren Positionen: Eine Position allein ergibt keinen Sinn, und die Summe der Bestellung muss immer zu den Positionen passen. Deshalb fasst man beides zusammen. Der zweite Satz ist die eigentliche Regel: Von außen ist nur die Wurzel sichtbar. Man greift nicht an einer Position vorbei in die Bestellung hinein — es gibt eine Tür, und die heißt Bestellung. Bei uns heißt sie Kartenautomat, und die Fächer liegen dahinter.
4:43 Die rechte Spalte macht aus abstrakten Eigenschaften handfeste Konsequenzen. Zwei Zeilen verdienen besondere Aufmerksamkeit. Die vierte — Einheit beim Laden und Speichern — ist der Grund, warum es im nächsten Modul genau ein Repository je Aggregat gibt und nicht je Klasse. Und die fünfte, die Konsistenzgrenze, ist die subtilste: Innerhalb des Aggregats muss alles sofort stimmen, jederzeit, ausnahmslos.
5:07 Über die Grenze hinaus darf Konsistenz auch später eintreten. Das ist keine Nachlässigkeit, sondern eine bewusste Entscheidung — und ohne sie würden verteilte Systeme gar nicht funktionieren. Der erste Punkt ist der eigentliche Grund, alles andere folgt daraus. Nur wenn jeder Zugriff durch die Wurzel läuft, lassen sich Invarianten zuverlässig durchsetzen — sonst gibt es immer einen Weg vorbei.
5:30 Der zweite formuliert es aus Sicht des Aufrufers: Er kann innere Entities gar nicht in einen ungültigen Zustand bringen, weil er sie nicht in die Hand bekommt. Das ist bequem für ihn und sicher für Sie. Der dritte Punkt ist der Alltagsvorteil: Anwendungsservices arbeiten mit einer Einheit statt mit Einzelteilen. Und der vierte ist das Ideal — am besten sind innere Entities von außen überhaupt nicht sichtbar.
5:53 Die ersten drei Punkte sind Varianten desselben Fehlers: die Grenze umgehen. Ob Sie eine innere Entity nach außen reichen, einen dauerhaften Verweis auf ein Fach halten oder Teile einzeln speichern — in allen Fällen ist die Wurzel nicht mehr die einzige Tür, und die Invarianten sind nur noch Empfehlung. Der vierte Punkt geht in die andere Richtung und ist der Übereifer-Fehler: übergreifende Invarianten sofort erzwingen wollen.
6:17 Wenn zwei Aggregate gemeinsam eine Regel erfüllen sollen, dürfen Sie das später herstellen — genau dafür gibt es Domain Events, die uns in Modul 7 beschäftigen.
Grenzen für Aggregate finden
6:26 Bleibt die praktische Frage: Wo verläuft die Grenze? Wie schon bei Entity und Value Object gibt es keine objektive Antwort — aber es gibt Fragen, die verlässlich weiterhelfen, und eine Zahl, die überrascht. Es gibt allerdings ein paar sehr brauchbare Fragen — und eine Zahl, die viele überrascht, weil sie deutlich kleiner ausfällt als erwartet.
6:46 Sie erkennen das Muster aus Modul 2 wieder: Wo ein Aggregat endet, entscheidet die Domäne. Dieselben Entities lassen sich in einem anderen Fachgebiet völlig anders gruppieren, und beide Zuschnitte können richtig sein. Die Leitgröße heißt Zusammenhalt: innen eng, nach außen lose. Das ist übrigens kein DDD- Spezialwissen, sondern derselbe Grundsatz, nach dem man auch Module oder Klassen schneidet — hohe Kohäsion, lose Kopplung.
7:12 Neu ist nur, dass die Grenze hier nicht nur eine Frage der Übersichtlichkeit ist, sondern darüber entscheidet, was gemeinsam gültig sein muss. Die erste Frage entscheidet die meisten Fälle: Ergibt die Entity ohne die andere Sinn? Wenn ja, wird sie eine eigene Wurzel. Die zweite Zeile überrascht regelmäßig — in der Praxis stecken ein bis drei Entities in einem Aggregat, selten mehr.
7:35 Wer mit sieben oder acht dasteht, hat vermutlich zu grob geschnitten. Die dritte Zeile ist eine Faustregel für Sammlungen: Ab etwa dreißig Einträgen wird das Nachladen teuer, dann macht man ein eigenes Aggregat daraus. Und die vierte ist die Betriebssicht — je größer das Aggregat, desto mehr gleichzeitige Änderungen kollidieren.
7:55 Value Objects zählen bei alldem nicht mit, davon dürfen es beliebig viele sein. Die ersten beiden Punkte wenden die Frage auf unser Beispiel an. Ein Fach ohne Automat ergibt keinen Sinn — es hängt buchstäblich im Gerät —, also gehört es hinein. Eine Wertkarte dagegen steht für sich; sie existiert weiter, wenn der Automat abgebaut wird, und wird deshalb eine eigene Wurzel.
8:17 Der dritte Punkt nennt den Preis großer Aggregate: mehr Kollisionen bei gleichzeitigen Änderungen, weil zwei Vorgänge dieselbe Einheit sperren. Und der vierte Punkt ist die Entlastung für alle, die Angst vor zu kleinen Zuschnitten haben: Viele Aggregate aus einer einzigen Entity sind völlig in Ordnung — das ist der Normalfall, nicht die Ausnahme.
8:37 Bei dieser Aufgabe geht es um mehr als Einteilen — es geht ums Begründen. Legen Sie für ein Modell die Aggregatgrenzen fest, jede mit der Frage nach dem eigenständigen Sinn begründet. Und dann der zweite, unbequeme Teil: Für mindestens eine Grenze führen Sie aus, was der andere Zuschnitt gekostet hätte. Das ist die Fähigkeit, auf die es im Projekt ankommt, denn genau so verlaufen Architekturdiskussionen.
9:00 Nehmen Sie bewusst eine Sammlung dazu, die im Betrieb stark wachsen kann — dort merken Sie, dass ein Zuschnitt, der heute elegant aussieht, in zwei Jahren die Anwendung ausbremsen kann.
Die Aggregate-Root-Basisklasse
9:12 Nach Entity und Value Object bekommt auch die Wurzel eine eigene Basisklasse. Sie ist die unspektakulärste der drei — und das Kapitel sagt auch offen, wann man auf sie verzichten sollte. Sie ist die unspektakulärste der drei Basisklassen dieses Seminars, und dieses Kapitel sagt offen, wann man ganz auf sie verzichten sollte.
9:31 Der wichtigste Halbsatz steht in der Mitte: Sie trägt keine Fachlogik. Diese Basisklasse ist Infrastruktur. Sie leistet zweierlei — sie macht die Grenze im Code sichtbar, sodass man einer Klasse ansieht, ob sie eine Wurzel ist, und sie bietet Platz für zwei Dinge, die später gebraucht werden: die Versionierung fürs optimistische Sperren und die Sammlung der Domain Events.
9:53 Beides sind technische Belange. Wer hier Fachlogik unterbringt, hat sie an der denkbar ungünstigsten Stelle — nämlich in einer Klasse, die alle erben. Drei Gründe — und ein vierter Punkt, der die anderen relativiert und den ich Ihnen besonders ans Herz lege. Die Basisklasse zeigt beim Lesen sofort, welche Entity eine Wurzel ist; das ist Dokumentation, die nicht veralten kann.
10:15 Sie trägt die Versionsnummer fürs optimistische Sperren, und sie sammelt ab Modul 7 die Domain Events. Und dann: Braucht das Projekt weder das eine noch das andere, lässt man sie weg. Das ist ein bemerkenswert nüchterner Satz für ein Architekturseminar — Bausteine baut man, wenn sie etwas tragen, nicht weil sie zum Muster gehören.
10:35 Zwei Klassen, und die interessante ist die untere. Oben die Basisklasse, noch fast leer — eine Versionsnummer und ein Kommentar, der auf Modul 7 verweist. Unten der Kartenautomat als Wurzel, und dort sehen Sie die Abschirmung in Code: Die Fächer sind privat, es gibt keinen Getter, der sie herausgibt. Was der Aufrufer bekommt, ist der Inhalt eines Fachs — und zwar als Kopie, nicht als Verweis.
10:59 Der Unterschied ist entscheidend: Mit einer Kopie kann niemand außerhalb den Zustand des Aggregats verändern. Nach außen geht nur der Inhalt, nie das Fach selbst. Der erste Punkt greift auf, was ich eben gesagt habe — eine Basisklasse, die nichts trägt, aber trotzdem überall geerbt wird, ist Zeremonie. Der zweite ist die Verwechslung von Infrastruktur und Fachlichkeit. Der dritte ist der klassische Grenzbruch: ein Getter, der die inneren Entities herausgibt.
11:27 Er entsteht fast immer aus einem gutgemeinten Testwunsch oder einer schnellen Anzeigeanforderung — und macht die Grenze dauerhaft löchrig. Und der vierte ist die Karteileiche: eine Versionsnummer pflegen, ohne optimistisches Sperren zu betreiben. Dann tragen Sie ein Feld mit, das niemand liest, und wecken bei Lesern eine falsche Erwartung.
Fehlende Anforderungen nachziehen
11:47 Jetzt setzen wir die offenen Regeln um — und zwar strikt eine nach der anderen. Die ersten beiden sind einfach. Der Reiz liegt im Ablauf, denn der ist bei jeder Fachregel derselbe und lohnt sich einzuprägen. Der Reiz liegt weniger in den Regeln selbst als im Ablauf, denn der ist bei jeder Fachregel derselbe und lohnt sich einzuprägen.
12:07 Die ersten beiden Regeln betreffen nur den Automaten selbst: Ein leeres Fach verkauft nichts, und ein Kauf über dem eingeworfenen Betrag kommt nicht zustande. Beide sind fachlich unstrittig. Interessant ist der letzte Halbsatz — beides gehört in die Wurzel, nicht in den Aufrufer. Es wäre so bequem, das in der Oberfläche zu prüfen, wo die Daten ohnehin liegen und die Fehlermeldung angezeigt werden muss. Aber dann gilt die Regel nur für diesen einen Weg.
12:33 Kommt morgen eine Schnittstelle dazu, gilt sie dort nicht. In der Wurzel gilt sie immer. Fünf Schritte, und ich würde Ihnen empfehlen, sich diese Reihenfolge einzuprägen — sie funktioniert für jede Fachregel. Erst der Satz in Fachsprache, bevor irgendjemand Code anfasst. Dann der Test, der scheitert. Dann die Prüfung an der Stelle, die die Invariante hält.
12:55 Schritt vier wird oft übersprungen und spart am meisten Arbeit: Prüfen, ob eine bestehende Invariante die Regel schon abdeckt. Bei uns ist das genau der Fall — die leere Stückzahl fällt bereits im Kartenstapel auf, wir müssen sie nicht noch einmal prüfen. Und Schritt fünf ist die Disziplin: erst dann zur nächsten Regel. Sechs Zeilen, und darin steckt die ganze Botschaft dieses Kapitels: Der Aufrufer prüft nichts. Er ruft „kaufen" auf, und alles Weitere entscheidet die Wurzel.
13:23 Ist das Fach leer, ist der Preis höher als das eingeworfene Geld — beides wird hier geklärt, an genau einer Stelle. Und achten Sie auf die Fehlertexte: „Fach ist leer", „Zu wenig Geld eingeworfen". Das sind fachliche Aussagen, keine technischen Zustände. Wer stattdessen eine Nummer oder einen technischen Code wirft, verliert genau die Information, die den Fehler verständlich macht — und zwar für den Entwickler ebenso wie für den Fachbereich.
13:50 Der erste Punkt ist die Bequemlichkeitsfalle, über die wir eben gesprochen haben. Der zweite ist der wichtigste beim testgetriebenen Arbeiten und begegnet uns zum zweiten Mal in diesem Seminar: ein Test, der auch ohne die neue Prüfung grün bleibt. Er kostet Sie nichts als Zeit — er prüft nichts. Der dritte Punkt ist eine Stilfrage mit Substanz: Fehlerfälle als Rückgabewerte zu kodieren verführt dazu, sie zu ignorieren, während eine Ausnahme sich nicht übersehen lässt.
14:16 Und der vierte ist wieder die Disziplin — zwei Regeln in einem Schritt, und beim nächsten roten Test wissen Sie nicht mehr, welche davon greift.
Die verborgene Anforderung
14:25 Und jetzt kommt der Teil, auf den ich mich in diesem Modul am meisten freue. Wir kommen zum Rückgeld — und stoßen dabei auf eine Frage, die vorher niemand gestellt hat, weil sie in keinem Anforderungsdokument steht. Es ist der Teil, an dem sich zeigt, was DDD von sorgfältiger Programmierung unterscheidet — und er beginnt mit einer scheinbar belanglosen Frage.
14:45 Die Frage lautet: Welche Stücke gibt der Automat eigentlich heraus? Man kann sie leicht überlesen, weil sie technisch beliebig scheint — Hauptsache, die Summe stimmt. Der Fachbereich sieht das anders und antwortet ohne Zögern: die großen. Kleine Stückelung bleibt im Automaten. Das ist eine Fachregel, die kein Entwickler erfunden hätte und die kein Anforderungsdokument enthält, weil sie für jeden, der solche Geräte betreibt, selbstverständlich ist.
15:12 Genau das sind die Regeln, die man nur im Gespräch findet — und die genau deshalb DDD ausmachen. Warum besteht der Fachbereich darauf? Rechnen Sie mit: Gibt der Automat immer kleine Münzen heraus, ist der Vorrat an Zehn- und Fünfzig-Cent-Stücken nach wenigen Käufen leer. Danach kann er nur noch Kunden mit passendem Betrag bedienen — ein Gerät, das dasteht und nichts verkauft.
15:34 Der dritte Punkt zeigt die Regel in Aktion: Wer fünf Ein-Euro-Stücke einwirft und für fünf Euro nichts kauft, bekommt einen Fünf-Euro-Schein zurück, nicht seine fünf Münzen. Und genau das gilt auch beim Abbruch. Zurück kommt der Wert, nicht dasselbe Geld — ein Satz, der gleich das Modell verändern wird. Der Algorithmus ist ein gieriges Verfahren: Sorten von groß nach klein durchgehen, von jeder so viel nehmen, wie sowohl der Restbetrag als auch der Vorrat hergeben, weiterrechnen.
16:03 Das entspricht genau dem, was ein Mensch an der Kasse tut. Worauf es ankommt, steht am Ende: Bleibt ein Rest übrig, wird eine Ausnahme geworfen. Der Automat gibt lieber gar nichts heraus, als eine falsche Zusammenstellung. Die Fußzeile bringt es auf den Punkt — bleibt ein Rest, ist der Kauf nicht möglich, nicht etwa fast möglich. Fast richtiges Rückgeld gibt es in dieser Domäne nicht.
16:26 Und hier ist die Belohnung. Wenn der Automat nicht dasselbe Geld zurückgibt, sondern nur denselben Wert, dann muss er das eingeworfene Geld gar nicht mehr getrennt aufbewahren. Aus zwei Geldbeträgen — Bestand und laufender Vorgang — wird ein Bestand plus ein eingeworfener Betrag. Die Unterscheidung aus Modul 2 war richtig gedacht und ist jetzt schlicht überflüssig geworden.
16:47 Das ist der Punkt, den ich Ihnen mitgeben möchte: Eine neue Fachregel hat das Modell vereinfacht, nicht verkompliziert. Wer die Domäne besser versteht, schreibt oft weniger Code — nicht mehr. Diese Aufgabe ist die anspruchsvollste des Moduls, weil eine Berechnung mit vielen Randfällen abzusichern ist. Drei Tests sind Pflicht: passender Betrag, fehlendes Rückgeld, leerer Vorrat einer Sorte — und alle drei müssen vor der Implementierung scheitern.
17:13 Der Hinweis nennt den interessantesten Fall, und den sollten Sie sich nicht entgehen lassen: Eine Sorte geht mittendrin aus, und der Algorithmus muss auf die nächstkleinere ausweichen. Genau dort trennt sich eine Implementierung, die zufällig funktioniert, von einer, die verstanden wurde.
Was du mitnimmst
17:30 Vier Punkte. Ein Aggregat wird am Stück geladen, geprüft und gespeichert — das ist seine Definition und zugleich die Vorlage für das nächste Modul. Die Wurzel schirmt die inneren Entities ab, sonst tragen die Invarianten nicht; ein einziger Getter reicht, um das auszuhebeln. Grenzen folgen dem Zusammenhalt, und ein bis drei Entities sind der Normalfall, nicht der Anfängerfehler.
17:52 Und der vierte Punkt ist der, den Sie in Ihr eigenes Projekt mitnehmen sollten: Eine Frage an den Fachbereich hat hier das Modell vereinfacht. Im nächsten Modul bauen wir mit den Repositories den Zugang zu diesem Aggregat.
Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →