Auf einen Blick
- Das Domänenhexagon (logische Sicht, DDD / Ports & Adapters) besteht aus den OO-Bausteinen: Werteobjekt, Entität, Identifizierer, Domain Service, Domain Event und Aggregat.
- Entität = Ding mit unveränderbarer Identität und Lebenszyklus (langlebig, persistiert, änderbar, wird NIE aus der Komponente exponiert); Werteobjekt = beschreibt/misst eine Entität, keine Identität, unveränderbar, kurzlebig, Gleichheit über Werte, darf exponiert werden.
- Durchgängiges Beispiel: NGHR (Personalabrechnung) mit Employee, Payroll, PayrollItem, TimeSheet — daran werden alle Konzepte durchgespielt.
- Identifizierer ist ein Werteobjekt, das eine Entität identifiziert; muss eindeutig und stabil sein, ggf. anwenderfreundlich; vier Erzeugungsstrategien: anwendererzeugt, anwendungsgeneriert (UUID), persistenzgeneriert, externe Quelle.
- Aggregat = Gruppe zusammengehöriger Entitäten/Werteobjekte, gruppiert nach Invarianten; genau eine Wurzelentität, transaktionale Konsistenz innerhalb, zukünftige Konsistenz zwischen Aggregaten, ein Repository pro Wurzelentität, eine Transaktion pro Aggregat.
- Eventbasierte Verarbeitung (Publish-Subscribe) und Domain Events entkoppeln Sender und Empfänger und fördern Wartbarkeit, Performanz, Skalierbarkeit und Robustheit.
Kernkonzepte
1. Werteobjekt (Value Object) – Definition und Rolle
Ein Werteobjekt misst oder beschreibt eine Entität oder enthält Informationen über eine Entität. Es ist einer der Grundbausteine der OO-Modellierung des Domänenhexagons. Beispiele bei NGHR: Personalnummer eines Mitarbeiters, Lebenszyklusstatus eines Mitarbeiters, Bankverbindung eines Mitarbeiters, Gehaltsauszahlungszeitpunkt der Gehaltsabrechnung. Werteobjekte haben keinen Identifizierer und keinen Lebenszyklus, sind nicht änderbar (immutable) und kurzlebig (Wegwerfobjekt). Gleichheit wird über die Werte definiert.
2. Werteobjekt – Modellierung nach OO-Designprinzipien
Fachliche Konzepte werden als Klasse modelliert. Kapselung: schütze die Daten, exponiere Verhalten. Erzeugung ist vollständig und konsistent (in einem Schritt), Invarianten werden erzwungen. Immutable macht die Verwendung sicher. Der Vergleich (equals) basiert auf allen Werten, der Hash Code ist konsistent zu equals. Ein Enum kann als Value Object dienen und fachliche Regeln deklarativ enthalten. Wichtig: Werteobjekte sind keine anämischen (blutleeren) Container für Daten — sie enthalten Wissen und Regeln der Domäne und haben Verhalten, wenn sinnvoll.
3. Werteobjekt – Test und Exposition
Getestet wird mit Unit Tests gegen die öffentliche Schnittstelle. Werteobjekte dürfen in der gesamten Anwendung UND in anderen Anwendungen exponiert werden. Das ist unproblematisch, weil Werteobjekte unveränderbar sind — niemand kann sie von außen in einen inkonsistenten Zustand bringen.
4. Entität (Entity) – Definition
Zitat: Eine Entität ist ein Ding, das primär durch eine Identität und einem Lebenszyklus definiert ist. Beispiele bei NGHR: Mitarbeiter, Gehaltsabrechnung, Position einer Gehaltsabrechnung, Stundenzettel. Keine Entitäten bei NGHR sind Name, Gehaltsklasse (monatlich / monatlich mit Provision / stundenweise) und Adresse — das sind Werteobjekte, die einen Mitarbeiter beschreiben. Zentral: Was eine Entität ist, hängt vom Kontext ab. Bei NGHR ist die Adresse ein Werteobjekt; in einer Kommunalverwaltung, die alle Adressen einer Stadt verwaltet, ist eine Adresse eine Entität.
5. Entität – Eigenschaften
Entitäten haben eine unveränderbare Identität; Gleichheit beruht ausschließlich auf der Identität. Sie haben einen Lebenszyklus (ein Ding ohne Lebenszyklus ist schwer vorstellbar) und werden durch Werteobjekte beschrieben. Erzeugung mindestens mit Identität und Lebenszyklusstatus. Sie enthalten das Verhalten der Geschäftsobjekte, erhalten ihre Invarianten und ändern Werteobjekte durch deren Austausch (weil Werteobjekte unveränderbar sind). Entitäten werden nach dem fachlichen Problem erstellt, nicht nach Bedürfnissen von UI, Persistenz oder Datenintegration. Sie sind langlebig, werden persistiert und werden NIE exponiert: Entitäten gehören genau einer Komponente; nur diese hat Zugriff. Test: Unit Tests der öffentlichen Schnittstelle.
6. Entität – Erzeugung
Vollständige und konsistente Erzeugung: mindestens Identifizierer und Lebenszykluszustand sind initialisiert, alle Invarianten sind eingehalten. Bei Erzeugung in mehreren Schritten gibt es drei Optionen: (1) Entwurfsmuster Builder, (2) eine eigene Entität, deren Zweck es ist, alle Informationen zu sammeln und dann die Ziel-Entität(en) zu erzeugen, (3) Erzeugung als eigener Lebenszyklusstatus (In Bearbeitung / Entwurf). Für die Erzeugung wird eine Factory-Methode verwendet, deren Name der allgegenwärtigen Sprache (ubiquitous language) folgt — nicht create oder generate.
7. Entität – Lebenszyklus und Zustandsautomat
Entitäten verwalten ihren Lebenszyklus selbst, mit Methoden, die das fachliche Ereignis einer Lebenszyklusänderung modellieren (z.B. retire, startFamilyLeave, activate, cancelContract) — NICHT mit einer Methode setStatus. Zustände zu identifizieren und zu modellieren ist nicht einfach: Vollständigkeit der Zustände, genau ein Initialzustand und ein oder mehrere wirklich finale Zustände, formale Korrektheit des Zustandsautomaten (Zustände ändern sich nur durch Ereignisse). Nicht alles, was mit einer Entität passiert, ist eine Änderung des Lebenszyklus. Es kann nötig sein, hierarchische Zustände zu modellieren (Beispiel CD-Player: obere Ebene Idle/Busy, untere Ebene Open/Closed bzw. Playing/Paused) oder einer Entität mehr als einen Status zu geben (Mobilfunkvertrag: kommerzieller Status und Lieferstatus). An den Zuständen kann man einen großen Teil der Geschäftslogik aufhängen.
8. Entität – Änderungen und Verhalten
Entitäten werden geändert, indem ihre Werteobjekte ausgetauscht werden (nicht setAddress, sondern Ausdruck der allgegenwärtigen Sprache). Da das eingesetzte Werteobjekt seine Invarianten selbst einhält, ist beim Austausch keine erneute Validierung nötig. Entitäten implementieren Verhalten — sie sind keine blutleeren Strukturen für Daten. Beispiel: In der abstrakten Basisklasse AbstractPayrollItem / konkreten Klasse IncomeTaxPayrollItemDE ist eine Invariante, dass nur in manchen Zuständen eine Neuberechnung erfolgt; das Verhalten hängt vom Lebenszyklus ab und der Lebenszyklus wird durch das Verhalten geändert (Implementierung einer Business Rule).
9. Entität – Regeln (Sparsamkeit und Isolation)
Nicht zu viele Entitäten, denn Entitäten sind aufwendig (Identifizierer, Lebenszyklus, Persistenz pro Entität). Werteobjekte bevorzugen, wenn eine Entität vermieden werden kann (potenziell wiederverwendbar, außerhalb der Komponente exponierbar). Änderungen nur im Domain Hexagon der Komponente, der die Entität gehört (Sicherstellung entitätsübergreifender Invarianten und Nachbedingungen). Entitäten sollen von nichts abhängen außer von ihren Werteobjekten — Grund: Wartbarkeit und insbesondere Testbarkeit (Ausnahme: Bibliotheken/Frameworks, wenn die Testbarkeit nicht beeinträchtigt wird). Nochmals: Entitäten werden niemals aus der Komponente herausgegeben, sonst könnten sie anderswo geändert werden, ohne Invarianten oder Nachbedingungen einzuhalten.
10. Identifizierer – Definition und Kriterien
Zitat: Ein Identifizierer ist ein Werteobjekt, das eine Entität identifiziert. Beispiele: Personalnummer (Integer 1234, String 001234, String EMP001234), Kombination aus Personalnummer und Datum für eine Gehaltsabrechnung (EMP001234-PAYR2020-08-01), UUID/GUID für eine Gehaltsabrechnungsposition. Kriterien: Er muss eindeutig sein (auch in verteilten Systemen), er muss stabil sein (darf sich nie ändern), und ggf. muss er benutzerfreundlich sein (gut lesbar und übermittelbar, aussagekräftig, natürliche Reihenfolge nach Erzeugungszeitpunkt).
11. Identifizierer – vier Erzeugungsstrategien
(1) Anwendererzeugt: Anwender gibt den Identifizierer ein (z.B. in der Domäne existierender Wert wie IBAN, ISBN, Steuernummer oder freie Wahl). (2) Anwendungsgeneriert: die Anwendung erzeugt ihn (Kombination aus MAC/IP-Adresse, Zeitstempel und Zähler; Pseudozufallszahl; UUID/GUID). (3) Persistenzgeneriert: ein Persistenz-Mechanismus (Datenbank) erzeugt ihn (Datenbanksequenz, Sequenztabellen). (4) Externe Quelle: ein anderes System/eine andere Anwendung gibt einen Wert vor (z.B. Identifizierer aus zentralem Stammdatenverwaltungssystem/CRM).
12. Identifizierer – Anwendererzeugt und Surrogatschlüssel
Anwendererzeugte Identifizierer sind problematisch, weil ein Identifizierer nicht änderbar ist: Tippfehler, real existierende Duplikate (nicht alle niederländischen Sozialversicherungsnummern und nicht alle ISBN sind eindeutig), Änderung der fachlichen Namensregeln. Abhilfen: Prüf- und Genehmigungsprozess (schwerfällig) oder Vermeidung durch einen Surrogatschlüssel: von der Anwendung vergeben, dem Anwender nicht angezeigt, nur er wird für Primär-/Fremdschlüssel, DB-Zugriffe und Schnittstellen verwendet; ein zweites Feld verhält sich gegenüber Anwendern als Identifizierer. Regel: anwendererzeugte Identifizierer vermeiden — Surrogatschlüssel verwenden.
13. Identifizierer – Persistenzgeneriert, UUID und externe Quelle
Persistenzgeneriert: Die Datenbank ist als zentrale Instanz geeignet, weil Entitäten immer persistiert werden, sie immer für Datenkonsistenz zuständig ist (ACID) und daher auch in verteilten Systemen eindeutige IDs liefern kann (Hochzählen bei Insert, Sequenzgeneratoren). Nachteile: Abhängigkeit von Infrastruktur (Stub/Fake für Tests nötig), Performanz. UUID/GUID (anwendungsgeneriert): Vorteile schnelle Erzeugung zur Laufzeit ohne zentrale Instanz; Nachteile in DB als Zeichenkette langsamer, nicht für Menschen gemacht, keine Reihenfolge nach Erzeugungszeitpunkt. Gut für Objekte, über die Anwender nicht reden (Events, Nachrichten, Positionen), für hohe Erzeugungsraten und als Surrogat. Es gibt keine Garantie der Eindeutigkeit, aber ein 128-Bit-UUID ist für alle praktischen Zwecke eindeutig. Externe Quelle: Vorteil Problem woanders gelöst; Nachteil Kopplung des wichtigsten Werteobjekts. Empfehlung: externen Identifizierer nicht als Identifizierer verwenden, sondern Surrogat, und ihn in zwei Zusatzfeldern speichern (External Reference und External Reference Source).
14. Identifizierer – Benutzerfreundlichkeit
Anwender verwenden Identifizierer häufig (am Telefon, in E-Mails/Chats/Tickets, in Excel-Datenexporten). Anwenderfreundliche Identifizierer sind für Menschen lesbar und nicht zu lang. Alphanumerische Sortierbarkeit sicherstellen (836, 1836 sortieren falsch; 0836, 1836 sortieren richtig — führende Nullen). Entitätstyp chiffrieren, entweder über Nummernkreise (die ersten 1-3 Ziffern, z.B. 10 für Mitarbeiter, 23 für Stundenzettel) oder über Präfixe (EMP-004567 für Mitarbeiter, TSH-0089379 für Stundenzettel). 1:n-Relationen abbilden (Gehaltsabrechnung für Mitarbeiter: EMP001234-PAYR2020-08-01).
15. Domain Service – Zweck und Beispiele
Wenn Domänenlogik nicht gut in Entitäten und deren Werteobjekte passt, verwendet man einen Domain Service. Beispiele: Konvertierung einer Entität in eine andere (Mitarbeiter aus System PZ in einen NGHR Employee; Buch-Vorschlag in ein Buch), Erzeugung von Entitäten (Factories, z.B. PayrollItem für jeden Gehaltstyp und jedes Land), Verarbeitung mehrerer Entitäten (Massenverrentung im Vorruhestandsprogramm), komplexe Geschäftslogik (komplexe Steuerberechnungen mit viel Code — Entitäten schlank halten, Fokus auf Identifizierer, Lebenszyklus, Invarianten) und Zugriff auf persistierte Daten oder externe Systeme.
16. Domain Service – Regeln und Aufrufszenarien
Domain Services sind Domänenobjekte im Domänenhexagon, nicht dazu da, von außerhalb der Komponente/Anwendung aufgerufen zu werden — sie haben mit Web Services nichts zu tun. Muss ein Domain Service auf das Applikationshexagon zugreifen, wird das DIP (Dependency Inversion Principle) verwendet. Domain Services sind Singletons (kein Client-bezogener Zustand); Abhängigkeiten zu anderen Singletons sind über Dependency Injection unproblematisch. Zwei Aufrufszenarien: Domain Service nutzt Entität (Applikationshexagon ruft mit Entität als Parameter) oder Entität nutzt Domain Service (Aufruf mit Domain Service als Parameter). Das Applikationshexagon versorgt Entität/Domain Service mit der jeweiligen Abhängigkeit. Antipattern: die Entität holt sich den Domain Service selbst aus einer Registry oder bekommt ihn per Dependency Injection injiziert — Grund: unnötige Abhängigkeiten, die Domäne ist nicht mehr isoliert.
17. Eventbasierte Verarbeitung (Exkurs) – Publish-Subscribe
Szenario: Ein Sender möchte beliebige Empfänger über ein Ereignis informieren und benötigt keine Rückmeldung (z.B. UI-Button; NGHR-Fall Mitarbeiter einstellen, worüber Controlling informiert werden muss). Zwei Lösungen: direkter Methodenaufruf oder ein eigenes Event-Objekt, das zentral publiziert wird, sodass registrierte Empfänger benachrichtigt werden. Die zweite Option ist aufwendiger, führt aber zur Entkopplung. Ablauf: Empfänger registriert sich beim EventPublisher (subscribe); Sender erzeugt ein Event-Objekt und veröffentlicht es (publish); der EventPublisher benachrichtigt alle registrierten Subscriber (notify über das Interface EventSubscriber mit handle(Event)). Entkopplung par excellence: Der Sender weiß nichts vom Empfänger — dieser muss noch nicht einmal existieren.
18. Eventbasierte Verarbeitung – Entkopplungsvorteile
Codeabhängigkeiten entkoppelt: Der Sender hängt nicht von den Empfängern ab; welche Empfänger wann registriert sind, ist ihm egal; Empfänger entscheiden selbst, ob und wie sie reagieren; das Konzept wird durch ein eigenes Event-Objekt (mit Verhalten und Daten) explizit gemacht. Laufzeitverhalten entkoppelt: Ohne Änderung des Senders können Events synchron oder asynchron, sequenziell oder parallel und remote verarbeitet werden. Eventbasierte Verarbeitung fördert Wartbarkeit und ist gut für Performanz, Skalierbarkeit und Robustheit.
19. Domain Event – Definition und Regeln
Ein Domain Event ist ein Ereignis, das für Fachexperten relevant ist (über das Fachexperten reden). Sprachliche Hinweise auf ein Domain Event: benachrichtigen, informieren, wenn... dann... Für fachlich relevante Ereignisse (z.B. Zustandsänderungen einer Entität) sollte ein Domain Event erzeugt werden. Domain Events sind unveränderbar und enthalten Informationen über ein in der Vergangenheit liegendes, abgeschlossenes Ereignis. Sie dienen NICHT dazu, die eigene Verarbeitung zu verfolgen (kein Ist bearbeitet-Status), weil das einen Vorteil der Eventverarbeitung zunichte machen würde. Domain Events können eine Identität haben, wenn sie persistiert werden müssen oder für die Weiterverarbeitung hilfreich ist (De-Duplikation mehrfach gesendeter Events). Namensgebung in der Vergangenheitsform der allgegenwärtigen Sprache: EmployeeRetired, PayrollPosted.
20. Domain Event – Einsatzszenarien und Implementierung
Einsatzszenario 1: Empfänger im Applikationshexagon — die Domäne informiert die Anwendung über ein Ereignis (Beispiel: Mitarbeiter eingestellt, Controlling wird informiert, Verarbeitung ruft Outbound Port auf). Einsatzszenario 2: Empfänger im Domänenhexagon — Kommunikation zwischen Entitäten eines Aggregats (Beispiel: PayrollItem löst Bruttoposition geändert aus, Payroll verarbeitet mit Neuberechnung). Für die Event-Identität ist die UUID die einzige sinnvolle Wahl (Anwender arbeiten nicht damit, gute Performanz, für De-Duplikation nötig, keine Persistenzabhängigkeit). Lebenszyklusereignisse werden veröffentlicht; damit bleibt die Entität auf ihren Lebenszyklus fokussiert und alle, die von dem Ereignis wissen müssen, werden lose gekoppelt informiert.
21. Aggregat – Motivation (NGHR-Beispiel)
Beispiel: Mitarbeiter mit stundenweiser Bezahlung; Modell Employee (1) — Payroll (0..n) — Payroll Item (1..n) — Time Sheet — Time Sheet Item. Szenario: Gehaltsabrechnung für den laufenden Monat ist angelegt, aber noch nicht gebucht; die Gehaltsart ändert sich rückwirkend Stundenweise -> Gehalt. Aktivitäten: Gehaltsklasse ändern, stundenweises Payroll Item löschen, Gehalts-Payroll-Item anlegen, Stundenzettel mit allen Positionen löschen. Wenn Relationen als Objektreferenzen abgebildet werden, muss zum Auflösen der gesamte Objektgraph geladen werden — hoher Ressourcenverbrauch, schlechte Performanz, wenig robust (ein Fehler blockiert alles). Verarbeitet man Entitäten einzeln, werden Invarianten verletzt (wer berechnet die Payroll neu? Inkonsistenz zur noch nicht geänderten Gehaltsklasse).
22. Aggregat – Definition und transaktionale Lösung
Lösung: Aufteilung in Transaktionen, sodass jeweils alle Entitäten geladen werden, die gemeinsame Invarianten haben, und zukünftige Konsistenz zwischen den Transaktionen. Definition: Ein Aggregat ist eine Menge zusammengehöriger Entitäten und Werteobjekte. Die Gruppierung erfolgt statisch und ist für jeden Anwendungsfall gleich (nicht anwendungsfallabhängig). Kriterium: Immer wenn transaktionale Konsistenz zwischen Entitäten nötig ist, werden diese zu einem Aggregat gruppiert. Datenbank-Constraints (Fremdschlüssel) verhindern die naive Aufteilung: Wird ein Payroll Item gelöscht, zeigt der Fremdschlüssel im Time Sheet ins Leere — deshalb Referenzen über Aggregatsgrenzen per Identifizierer statt per Fremdschlüssel.
23. Aggregat – Wurzelentität (Root Entity)
Jedes Aggregat hat genau eine Wurzelentität. Sie ist verantwortlich für die Einhaltung aller Invarianten innerhalb des Aggregats (z.B. Bruttogehalt der Payroll = Summe der Bruttopositionen). Folge 1: Sie ist die einzige öffentliche Schnittstelle des Aggregats — nur so kann sie ihrer Verantwortung gerecht werden (Bezug zum Gesetz von Demeter und zum Entwurfsmuster Fassade). Folge 2: Nur sie darf von außerhalb des Aggregats referenziert werden (es macht keinen Sinn, etwas zu referenzieren, das man nicht verwenden darf).
24. Aggregat – Regeln (Zusammenfassung der Konstruktionsregeln)
Gruppierung: Entitäten, die zu jedem Zeitpunkt übergreifende Invarianten einhalten müssen, kommen in ein Aggregat; andere in verschiedene. Kleine Aggregate fördern Performanz und Skalierbarkeit (weniger Konflikte/Sperren), brauchen weniger Ressourcen und sind besser wartbar. Konsistenz: transaktionale Konsistenz innerhalb, zukünftige Konsistenz zwischen Aggregaten. Relationen: innerhalb Objektreferenzen und Fremdschlüssel, zwischen Aggregaten Referenzen über Identifizierer. Persistenz: immer das gesamte Aggregat lesen/speichern/löschen, bezogen auf die Wurzelentität — pro Wurzelentität ein Repository. Transaktionen: ein Aggregat wird in einer Transaktion geändert, eine Transaktion ändert genau ein Aggregat. Sperren erfolgen immer für das gesamte Aggregat auf der Root Entity.
25. Aggregat – Regeln brechen, Praxis, DDD-Einordnung
Gründe, die Regel eine Transaktion ein Aggregat zu brechen: Brauchbarkeit der Benutzerschnittstelle (Massenänderungen, Nachfolgeobjekte, akzeptabel solange keine Invarianten gefährdet sind), Fehlen technischer Mechanismen für zukünftige Konsistenz, Vorgaben der Organisation (bis zu verteilten Transaktionen, weil DB-Mechanismen bewährt sind), Abfrageperformanz (fehlende PK-FK-Relationen über Grenzen machen Abfragen langsam — Alternative CQRS). Praxis: globale vs. lokale Identität (nur die Wurzelentität braucht globale Identität; Nicht-Wurzelentitäten können lokale Identitäten haben und dürfen rausgegeben, aber nicht referenziert werden); eine Transaktion darf mehrere Aggregate lesen, aber nicht anlegen/ändern/löschen. Aggregate lösen IT-Probleme (Invarianten, Performanz/Skalierbarkeit/Robustheit, Relationen, Laden/Speichern, Transaktionen/Sperren) in domänengetriebener Weise — Gruppierung nach fachlichen Kriterien (Invarianten), nicht nach technischen Problemen oder UI/DB-Anforderungen.
26. Layer Supertype (Exkurs) und Referenzarchitektur
Layer Supertype (Enterprise-Muster nach Fowler, Patterns of Enterprise Architecture): ein Basistyp für alle Objekte, die eine bestimmte Rolle in einer Schicht/einem Hexagon einnehmen. Vorteile: Klarheit (Abbildung von Design- und Architekturregeln im Code), keine Code-Duplizierung (eine Heimat für gemeinsames Verhalten), typsicher, suchbar (IDE, Javadoc). Beispielhierarchie: DomainObject als Wurzel; darunter ValueObject, Entity<Identifier> (und AbstractEntity, RootEntity), Identifier<T> (mit AbstractIdentifier, AbstractUUIDIdentifier), DomainService, DomainEvent, DomainEventPublisher, DomainEventSubscriber. In der Referenzarchitektur der logischen Sicht besteht jede Komponente aus drei Ringen: Infrastructure (*.infra, mit Inbound/Outbound Adapter), Application (*.application, mit Inbound/Outbound Port) und Domain (*.domain, mit Wurzelentität, Aggregat, Entität, Wertobjekt, Domain Service, Domain Event). Codeabhängigkeiten zeigen von außen nach innen.
Merksätze
Klausur-Stichworte (aus der Zusammenfassung)
Theißen hat gesagt: alle prüfbaren Themen tauchen als Stichwort in den Zusammenfassungs-Folien auf. Genau diese Stichworte stehen hier.
- Werteobjekt: misst oder beschreibt eine Entität oder enthält Informationen über eine Entität
- Werteobjekt: kurzlebig — bei Wertänderung wird durch ein neues ersetzt
- Werteobjekt: wird als Teil einer Entität persistiert
- Werteobjekt: hat Verhalten, wenn sinnvoll
- Werteobjekt: unveränderbar, kein Lebenszyklus
- Werteobjekt: keine Identität, Gleichheit durch Werte
- Werteobjekt: kann aus der Komponente rausgegeben werden — sicher, weil nicht änderbar
- Entität: ein Ding, das primär durch eine Identität und einen Lebenszyklus definiert ist
- Entität: wird persistiert, langlebig
- Entität: implementiert das Verhalten eines Geschäftsobjekts
- Entität: änderbar durch Austausch der Werteobjekte
- Entität: Gleichheit durch Identität definiert
- Entität: wird nie aus der Komponente herausgegeben — nur ein Domänenhexagon ist für Änderungen zuständig
- Identifizierer: Eigenschaften eindeutig, stabil, ggf. anwenderfreundlich
- Identifizierer: Erzeugung durch Anwender, Anwendung, Infrastruktur oder externes System
- Domain Service: eine Heimat für Domänenlogik, die nicht in eine Entität passt
- Domain Service: Konvertieren/Erzeugen von Entitäten, Verarbeitung mehrerer Entitäten gleichen Typs, komplexe Domänenlogik
- Domain Service: Aufruf aus Applikationshexagon oder aus Entität
- Eventbasierte Verarbeitung: sorgt für lose Kopplung (Codeabhängigkeiten minimiert — Wartbarkeit gut)
- Eventbasierte Verarbeitung: Implementierung durch das Entwurfsmuster Publish-Subscribe
- Eventbasierte Verarbeitung: Laufzeitverhalten vom Aufrufer entkoppelt (synchron/asynchron, lokal/remote)
- Eventbasierte Verarbeitung: verbessert Performanz, Skalierbarkeit und Robustheit
- Domain Event: ein Ereignis, über das Fachexperten reden
- Domain Event: Einsatzszenario 1 — Entität benachrichtigt Applikationshexagon über ein Ereignis
- Domain Event: Einsatzszenario 2 — Kommunikation zwischen Entitäten eines Aggregats
- Aggregat: eine Gruppe von Entitäten, deren Invarianten zu jedem Zeitpunkt eingehalten werden müssen
- Aggregat: kleine Aggregate sind gut für Performanz, Skalierbarkeit, Wartbarkeit, Ressourcenverbrauch
- Aggregat: transaktionale Konsistenz innerhalb, zukünftige Konsistenz zwischen Aggregaten
- Aggregat: eine Wurzelentität pro Aggregat (Einhaltung der Invarianten, einzige öffentliche Schnittstelle, nur sie von außen referenzierbar)
- Aggregat: innerhalb Objektreferenzen, zwischen Aggregaten Referenzen über Identifizierer
- Aggregat: pro Wurzelentität ein Repository — immer das gesamte Aggregat lesen/schreiben/löschen
- Aggregat: ein Aggregat — eine Transaktion
- Aggregat: Sperren auf der Root Entität für das gesamte Aggregat
Übungsfragen
F1 Worin unterscheiden sich Entität und Werteobjekt in Bezug auf Identität, Änderbarkeit, Lebenszyklus und Gleichheit?
Eine Entität hat einen Identifizierer, einen Lebenszyklus, ist änderbar und langlebig; ihre Gleichheit ist ausschließlich durch die Identität definiert. Ein Werteobjekt hat keinen Identifizierer und keinen Lebenszyklus, ist unveränderbar (immutable) und kurzlebig; seine Gleichheit ist durch alle seine Werte definiert. Beide besitzen Verhalten bzw. Domänenlogik.
F2 Warum dürfen Werteobjekte aus einer Komponente exponiert werden, Entitäten aber nicht?
Werteobjekte sind unveränderbar, deshalb kann niemand sie von außen in einen inkonsistenten Zustand versetzen — ihre Herausgabe ist unproblematisch. Entitäten sind dagegen änderbar; würden sie herausgegeben, könnten sie außerhalb ihrer Komponente geändert werden, ohne dass entitätsübergreifende Invarianten oder Nachbedingungen eingehalten werden. Nur das Domänenhexagon der besitzenden Komponente darf eine Entität ändern.
F3 Was bedeutet die Aussage, dass es kontextabhängig ist, ob ein Konzept eine Entität oder ein Werteobjekt ist? Nennen Sie das Beispiel aus der Vorlesung.
Ob etwas eine eigene Identität und einen Lebenszyklus braucht, hängt vom fachlichen Kontext (Bounded Context) ab. Bei NGHR ist die Adresse eines Mitarbeiters ein Werteobjekt, das den Mitarbeiter beschreibt. In einer Anwendung der Kommunalverwaltung, die alle Adressen einer Stadt verwaltet, ist eine Adresse dagegen eine Entität mit eigener Identität.
F4 Was besagt die allgegenwärtige Sprache (ubiquitous language) für die Modellierung von Methoden an Entitäten?
Methoden sollen das fachliche Ereignis modellieren und nicht technisch benannt sein. Für Lebenszyklusänderungen verwendet man Namen wie retire, activate, startFamilyLeave oder cancelContract statt setStatus, und für Änderungen tauscht man Werteobjekte mit fachlich benannten Methoden aus statt setAddress. Auch Factory-Methoden heißen nicht create oder generate, und Domain Events werden in der Vergangenheitsform benannt (EmployeeRetired, PayrollPosted).
F5 Warum verwaltet eine Entität ihren Lebenszyklus über fachliche Ereignismethoden statt über eine setStatus-Methode?
Weil Zustandsänderungen im Zustandsautomaten nur durch klar definierte fachliche Ereignisse erfolgen dürfen und an diesen Zuständen ein großer Teil der Geschäftslogik hängt. Eine generische setStatus-Methode würde beliebige, auch ungültige Zustandsübergänge erlauben und die Invarianten sowie die formale Korrektheit des Zustandsautomaten (genau ein Initialzustand, wirklich finale Zustände) untergraben.
F6 Welche drei Optionen nennt die Vorlesung, wenn eine Entität in mehreren Schritten erzeugt werden muss?
Erstens das Entwurfsmuster Builder. Zweitens eine eigene Entität, deren Zweck es ist, alle Informationen zu sammeln und daraus dann die Ziel-Entität(en) zu erzeugen. Drittens die Erzeugung als eigener Lebenszyklusstatus wie In Bearbeitung oder Entwurf. In jedem Fall gilt: vollständige und konsistente Erzeugung mit mindestens Identifizierer und Lebenszykluszustand und Einhaltung aller Invarianten.
F7 Wie definiert die Vorlesung einen Identifizierer, und welche Kriterien muss er erfüllen?
Ein Identifizierer ist ein Werteobjekt, das eine Entität identifiziert. Er muss eindeutig sein (auch in verteilten Systemen) und stabil sein, das heißt sich nie ändern. Zusätzlich kann er benutzerfreundlich sein müssen: gut lesbar und übermittelbar, aussagekräftig hinsichtlich des Objekttyps und mit natürlicher Reihenfolge nach Erzeugungszeitpunkt.
F8 Welche vier Strategien zur Erzeugung von Identifizierern gibt es, und wodurch unterscheiden sie sich?
Anwendererzeugt (der Anwender gibt ihn ein, z.B. IBAN/ISBN/Steuernummer), anwendungsgeneriert (die Anwendung erzeugt ihn, z.B. UUID/GUID oder MAC/IP plus Zeitstempel und Zähler), persistenzgeneriert (die Datenbank erzeugt ihn per Sequenz oder Hochzählen bei Insert) und externe Quelle (ein anderes System gibt den Wert vor). Sie unterscheiden sich darin, wer den Wert vergibt und welche Abhängigkeiten und Nachteile dadurch entstehen.
F9 Warum sind anwendererzeugte Identifizierer problematisch, und wie hilft ein Surrogatschlüssel?
Weil ein Identifizierer nicht änderbar ist, sind Tippfehler, real existierende Duplikate (z.B. nicht alle ISBN oder niederländischen Sozialversicherungsnummern sind eindeutig) und Änderungen der fachlichen Namensregeln problematisch. Ein Surrogatschlüssel wird von der Anwendung vergeben, dem Anwender nicht angezeigt und allein für Primär-/Fremdschlüssel, DB-Zugriffe und Schnittstellen verwendet; ein zweites Feld verhält sich gegenüber Anwendern als Identifizierer. Regel: anwendererzeugte Identifizierer vermeiden, Surrogatschlüssel verwenden.
F10 Wofür eignen sich UUIDs besonders und welche Nachteile haben sie?
UUIDs/GUIDs eignen sich für Objekte, über die Anwender nicht reden (Events, Nachrichten, Positionen einer Buchung oder Gehaltsabrechnung), für hohe Erzeugungsraten und als Surrogat, weil sie schnell zur Laufzeit ohne zentrale Instanz erzeugt werden. Nachteile: als Zeichenkette in der Datenbank etwas langsamer, nicht für Menschen lesbar und ohne Reihenfolge nach Erzeugungszeitpunkt. Eine Eindeutigkeit ist nicht garantiert, aber ein sinnvoll erzeugter 128-Bit-UUID ist für alle praktischen Zwecke eindeutig.
F11 Wann verwendet man laut Vorlesung einen Domain Service, und welche Regeln gelten dafür?
Einen Domain Service verwendet man, wenn Domänenlogik nicht gut in eine Entität oder deren Werteobjekte passt, etwa zur Konvertierung oder Erzeugung von Entitäten, zur Verarbeitung mehrerer Entitäten, für komplexe Geschäftslogik oder Zugriff auf persistierte/externe Daten. Domain Services sind Domänenobjekte im Domänenhexagon, werden nicht von außen aufgerufen (nichts mit Web Services zu tun), sind Singletons ohne Client-Zustand und nutzen bei Zugriff auf das Applikationshexagon das Dependency Inversion Principle.
F12 Was ist das Antipattern bei der Bereitstellung von Domain Services an Entitäten, und wie wird es korrekt gelöst?
Antipattern ist, dass sich die Entität den Domain Service selbst aus einer Registry holt oder ihn per Dependency Injection injiziert bekommt. Das erzeugt unnötige Abhängigkeiten und die Domäne ist nicht mehr isoliert. Korrekt versorgt das Applikationshexagon die Entitäten mit allen nötigen Abhängigkeiten, indem es den Domain Service als Parameter übergibt.
F13 Wie funktioniert das Publish-Subscribe-Muster bei der eventbasierten Verarbeitung, und warum entkoppelt es Sender und Empfänger?
Der Empfänger registriert sich beim EventPublisher (subscribe). Der Sender erzeugt ein Event-Objekt und veröffentlicht es (publish). Der EventPublisher benachrichtigt daraufhin alle registrierten Subscriber über deren handle-Methode. Dadurch weiß der Sender nichts von den Empfängern — diese müssen noch nicht einmal existieren; er hängt nicht von ihnen ab, und Empfänger entscheiden selbst, ob und wie sie reagieren.
F14 Welche Freiheiten beim Laufzeitverhalten ermöglicht die eventbasierte Verarbeitung, ohne den Sender zu ändern?
Events können synchron oder asynchron, sequenziell oder parallel sowie remote verarbeitet werden, ohne dass der Sender geändert werden muss. Dadurch fördert die eventbasierte Verarbeitung Wartbarkeit und verbessert Performanz, Skalierbarkeit und Robustheit einer Anwendung deutlich.
F15 Was ist ein Domain Event, und warum darf es keinen Ist-bearbeitet-Status enthalten?
Ein Domain Event ist ein für Fachexperten relevantes, unveränderbares Ereignis, das ein in der Vergangenheit liegendes, abgeschlossenes Geschehen beschreibt und in Vergangenheitsform benannt wird. Es darf keinen Verarbeitungsstatus enthalten, weil es sonst nicht mehr unveränderbar wäre und ein Vorteil der Eventverarbeitung — die Entkopplung, bei der der Sender nichts über die Verarbeitung durch die Empfänger wissen muss — zunichtegemacht würde.
F16 Nennen und erläutern Sie die beiden Einsatzszenarien für Domain Events.
Szenario 1: Der Empfänger sitzt im Applikationshexagon — die Domäne informiert die Anwendung über ein Ereignis, etwa wenn ein Mitarbeiter eingestellt wurde und die Verarbeitung einen Outbound Port aufruft, um das Controlling zu informieren. Szenario 2: Der Empfänger sitzt im Domänenhexagon — es handelt sich um Kommunikation zwischen Entitäten eines Aggregats, etwa wenn ein PayrollItem das Ereignis Bruttoposition geändert auslöst und Payroll darauf mit einer Neuberechnung reagiert.
F17 Was ist ein Aggregat, und nach welchem Kriterium werden Entitäten zu einem Aggregat gruppiert?
Ein Aggregat ist eine Menge zusammengehöriger Entitäten und Werteobjekte. Die Gruppierung erfolgt statisch und ist für jeden Anwendungsfall gleich. Kriterium ist die transaktionale Konsistenz: Entitäten, deren übergreifende Invarianten zu jedem Zeitpunkt eingehalten werden müssen, kommen in dasselbe Aggregat; Entitäten, die das nicht müssen, in verschiedene Aggregate.
F18 Welche Verantwortung und Sonderstellung hat die Wurzelentität eines Aggregats?
Jedes Aggregat hat genau eine Wurzelentität. Sie ist verantwortlich für die Einhaltung aller Invarianten innerhalb des Aggregats (z.B. dass das Bruttogehalt der Payroll der Summe der Bruttopositionen entspricht). Sie ist die einzige öffentliche Schnittstelle des Aggregats (Bezug zum Gesetz von Demeter und zum Fassade-Muster) und darf als Einzige von außerhalb des Aggregats referenziert werden.
F19 Wie werden Relationen innerhalb und zwischen Aggregaten abgebildet, und warum?
Innerhalb eines Aggregats werden Relationen über Objektreferenzen im Speicher und Fremdschlüssel in der Datenbank abgebildet. Zwischen Aggregaten werden sie nur über Identifizierer (Werte) abgebildet. Grund: Fremdschlüssel über Aggregatsgrenzen würden das unabhängige Erzeugen und Löschen wegen Datenbank-Constraints verhindern — beim Löschen eines Payroll Items zeigte ein Fremdschlüssel im Time Sheet sonst ins Leere.
F20 Welche Regeln gelten für Persistenz, Transaktionen und Sperren bei Aggregaten?
Es wird immer das gesamte Aggregat gelesen, gespeichert oder gelöscht, bezogen auf die Wurzelentität; pro Wurzelentität gibt es ein Repository. Ein Aggregat wird in einer Transaktion geändert, und eine Transaktion ändert genau ein Aggregat (sonst drohen Inkonsistenzen bzw. zu große Transaktionen). Sperren erfolgen immer für das gesamte Aggregat auf der Root Entity. Eine Transaktion darf aber mehrere Aggregate lesen — nur nicht anlegen, ändern oder löschen.
F21 Was bedeuten transaktionale Konsistenz und zukünftige Konsistenz im Zusammenhang mit Aggregaten?
Transaktionale Konsistenz gilt innerhalb eines Aggregats: alle Invarianten werden in einer Transaktion sofort und garantiert eingehalten. Zukünftige (eventual) Konsistenz gilt zwischen verschiedenen Aggregaten: Änderungen breiten sich zeitversetzt aus, sodass die Aggregate erst später wieder konsistent zueinander sind. Diese Aufteilung erlaubt kleine, unabhängige Aggregate mit besserer Performanz und Skalierbarkeit.
F22 Warum werden Aggregate als domänengetriebene Lösung technischer Probleme bezeichnet?
Aggregate lösen typische IT-Probleme wie Invarianten, Performanz/Skalierbarkeit/Robustheit, Relationen mit Objektreferenzen und Fremdschlüsseln, Laden/Speichern sowie Transaktionssteuerung und Sperren. Der domänengetriebene Kern ist, dass die Gruppierung des Domänenmodells nach fachlichen Kriterien (nämlich den einzuhaltenden Invarianten) erfolgt und nicht nach den technischen Problemen oder den Anforderungen von UI und Datenbank.
F23 Was ist ein Layer Supertype und welche Vorteile bringt er?
Ein Layer Supertype (Enterprise-Muster nach Fowler) ist ein Basistyp für alle Objekte, die eine bestimmte Rolle in einer Schicht oder einem Hexagon einnehmen, zum Beispiel DomainObject als Wurzel und darunter ValueObject, Entity, Identifier, DomainService oder DomainEvent. Vorteile sind Klarheit (Design- und Architekturregeln werden im Code abgebildet), keine Code-Duplizierung (eine Heimat für gemeinsames Verhalten), Typsicherheit und gute Auffindbarkeit über IDE und generierte Dokumentation.
F24 Aus welchen Gründen darf man laut Vorlesung die Regel eine Transaktion ein Aggregat brechen?
Erstens für die Brauchbarkeit der Benutzerschnittstelle (z.B. Massenänderungen oder Nachfolgeobjekte, sofern keine Invarianten gefährdet sind), zweitens wenn technische Mechanismen für zukünftige Konsistenz fehlen, drittens wegen Vorgaben der Organisation (bis hin zu verteilten Transaktionen, weil bewährte DB-Mechanismen sicherer sind als selbstgebaute Konsistenz) und viertens aus Gründen der Abfrageperformanz, für die alternativ CQRS eingesetzt werden kann.