Domain-Driven Design (DDD) entstand mit Eric Evans’ Buch von 2003 als Antwort auf ein häufiges Problem: Komplexe Software bildet die Fachdomäne unzureichend ab. Wenn der Code andere Begriffe nutzt als die Fachexperten, häufen sich Fehler, weil Missverständnisse entstehen, und jede Änderung wird riskant. DDD stellt Werkzeuge bereit, mit denen wir Komplexität an ihrer Quelle zähmen. Statt zu versuchen, alle Bereiche in ein einheitliches Modell zu pressen, akzeptiert DDD, dass unterschiedliche Unternehmensbereiche unterschiedliche Begriffe verwenden, und zieht deshalb klare Grenzen.
Strategisches DDD: Bounded Contexts
Das zentrale Konzept von DDD ist der Bounded Context. Er ist eine ausdrückliche Grenze, innerhalb derer ein bestimmtes Domänenmodell gilt. Außerhalb dieser Grenze darf derselbe Begriff etwas anderes bedeuten.
In einem E‑Commerce‑System kann ein „Produkt“ verschiedene Bedeutungen haben:
- im Katalogkontext ist es ein Objekt mit Name, Beschreibung und Preis.
- im Lagerkontext hat es Maße, Gewicht und Bestand.
- im Kundenservicekontext ist es der Artikel, über den sich ein Kunde beschwert.
Jeder Kontext besitzt sein eigenes Modell. Teams vermeiden häufig das Teilen von Datenbanktabellen oder Domänenklassen über diese Grenzen hinweg und kommunizieren stattdessen über explizite Verträge.
Wann Bounded Contexts sinnvoll sind
Bounded Contexts erweisen sich als besonders nützlich, wenn unterschiedliche Teams verschiedene Teile der Domäne verantworten. Ebenso dann, wenn derselbe Begriff in verschiedenen Bereichen unterschiedliche Bedeutungen trägt, wenn sich Teilbereiche aus unterschiedlichen geschäftlichen Gründen ändern oder wenn eine Aufteilung in Microservices vorbereitet wird. Wenn Module eines Monolithen selten gemeinsam geändert werden, sind sie gute Kandidaten für getrennte Kontexte. Durch die Trennung reduziert sich der Welleneffekt von Änderungen, weil eine Modifikation in einem Modul selten eine Anpassung eines anderen erfordert.
Taktisches DDD: Die Bausteine
Nachdem die Kontexte definiert sind, ermöglichen taktische Muster, das Innere jedes Kontextes präzise zu modellieren.
Entitäten
Eine Entität besitzt eine eigene Identität, die im Laufe der Zeit erhalten bleibt. Zwei Objekte mit identischen Attributen sind nicht dasselbe, wenn ihre IDs verschieden sind. Ein Kunde mit der ID abc123 bleibt dieselbe Entität, auch wenn sich sein Name ändert. Eine Adresse ohne ID ist typischerweise ein Wertobjekt.
Wertobjekte
Ein Wertobjekt hat keine eigene Identität. Es wird vollständig durch seine Attribute definiert. Zwei Adressen mit derselben Straße, Stadt und Postleitzahl sind austauschbar. Wertobjekte sind unveränderlich. Statt ein existierendes Wertobjekt zu verändern, sollte man ein neues, gleichwertiges Objekt erzeugen; diese Unveränderlichkeit verhindert, dass ein Wert heimlich hinter den Kulissen wechselt.
Aggregate
Ein Aggregate definiert eine Konsistenzgrenze um eine oder mehrere Entitäten. Genau eine Entität ist die Aggregate Root. Externer Code greift nur über diese Wurzel auf das Aggregate zu. Das Aggregate stellt sicher, dass seine Invarianten stets gelten. Wird einem bereits platzierten Auftrag ein Artikel hinzugefügt, schlägt die Operation fehl, denn nach dem Platzieren hat sich sein Zustand von „Entwurf“ zu „Bestellt“ geändert und das Hinzufügen würde die Geschäftsregel verletzen.
Aggregate in der Praxis: Klein ist schön
In Systemen mit hohem Durchsatz können große Aggregate zu Sperr‑Hotspots werden. Es ist ratsam, kleine Aggregate zu entwerfen, in denen nur Daten aufgenommen werden, die in einer Transaktion konsistent bleiben müssen. Stattdessen sollten andere Aggregate über IDs referenziert werden, etwa droneId und packageId statt direkter Objekte. Eine eventual consistency sollte eingesetzt werden, wenn sofortige Konsistenz nicht zwingend erforderlich ist. Domain Events sind eine verbreitete Option für Prozesse über mehrere Aggregate; sie ermöglichen es, Änderungen asynchron zu propagieren, ohne die Kopplung zwischen den Aggregaten zu erhöhen.
Domain Events
Aggregate können Domain Events auslösen, wenn sie ihren Zustand ändern. Andere Systemteile abonnieren diese Ereignisse. So bleibt das Delivery‑Aggregate davon unabhängig, welche anderen Komponenten sich für den Abschluss interessieren; es meldet lediglich das Ereignis, und jede interessierte Komponente kann darauf reagieren.
Strategische Muster: Context Maps
Wenn mehrere Kontexte zusammenarbeiten, beschreibt eine Context Map ihre Beziehung. Diese Muster geben uns ein Vokabular, um die Art der Integration zwischen den Kontexten klar zu benennen und zu planen.
- Customer/Supplier: Ein Kontext liefert, der andere konsumiert; beide stimmen die APIs ab.
- Conformist: Eine Seite übernimmt das Modell der anderen.
- Anticorruption Layer: Ein Kontext kapselt ein externes System und übersetzt dessen Modell.
- Open Host Service: Ein Kontext veröffentlicht ein Protokoll für andere.
- Shared Kernel: Zwei Kontexte teilen ein kleines gemeinsames Modell.
Wann DDD übertrieben ist
DDD bringt zusätzlichen Aufwand mit sich. Es braucht engagierte Fachexperten, die bereit sind, sich in die Modellierung einzubringen, sowie eine Teamkultur der Zusammenarbeit und Disziplin bei der Wahrung der Grenzen zwischen Kontexten. In einfachen CRUD‑Anwendungen ohne komplexe Regeln, in kurzlebigen Projekten mit geringem Wartungsbedarf oder in MVPs, deren Domäne noch kaum verstanden ist, überwiegt der Nutzen der Modellierung nicht den Aufwand, weil die Logik wenig Tiefe besitzt und Änderungen selten auftreten. DDD eignet sich jedoch besonders für komplexe und langlebige Systeme sowie größere Teams oder Teams, die nach Domänen aufgeteilt sind. Dort sorgt die explizite Modellierung für Klarheit, reduziert Fehlkommunikation und ermöglicht ein kontrolliertes Wachstum des Codes.
Beispiel: E‑Commerce mit DDD
// Jedes ist eine Aggregate Root mit eigenem Lebenszyklus
class Customer {
addOrder(order: OrderId): void { /* ... */ }
}
class Order {
addItem(product: ProductId, qty: number): void { /* ... */ }
place(): void { /* ... */ }
}
class Product {
adjustPrice(newPrice: Money): void { /* ... */ }
}
Jedes Aggregate verwaltet seine eigenen Invarianten. Kunde und Auftrag sind getrennte Aggregate, weil sie unterschiedliche Lebenszyklen und Konsistenzanforderungen haben. Durch die klare Trennung der Aggregate bleibt jedes Teilgebiet autonom und kann unabhängig weiterentwickelt werden, während die Geschäftsregeln durch die jeweiligen Aggregate garantiert werden.

Comments (0)
Please sign in to leave a comment