Lose Koppelung durch Messaging und Domain Events – Teil 1

Teil 1 – Aggregate mittels Domain Events entkoppeln und synchronisieren

Ausgangssituation

Viele Prinzipien objektorientieren Software-Designs zielen auf Abstraktion und Entkoppelung hin. Ziel ist dabei unter anderem, dass Komponenten möglichst wenig Abhängigkeiten besitzen und somit leichter verständlich sowie einfacher weiter zu entwickeln sind. In der Realität sieht man oft das Gegenteil. Im Lebenszyklus eines Softwaresystems mehren sich die Abhängigkeiten zwischen Softwarekomponenten. Das macht den Code schwer wartbar, da Änderungen in einer Komponente dann zwangsläufig auch Änderungen in anderen Komponenten erfordern. Dies gilt es aber unbedingt zu vermeiden.

Bei der Anwendung von Domain-Driven Design (DDD) Prinzipien kommt schnell bei der Implementierung von Aggregaten im Entwicklerteam die berechtigte Frage auf, wie wird ein Aggregat von der Aktualisierung eines anderen benachrichtigt? Stellen wir uns vor, wir haben folgende fachliche Anforderung zu erfüllen:

„Ändert sich die Adresse eines Kunden, so muss der zuständige Sachbearbeiter per Email benachrichtigt werden.“

Gehen wir davon aus, dass wir Customer als auch DeskOfficer als Aggregate Root identifiziert haben.

Bei einer solch einfach anmutenden Anforderung werden in der Realität in der Regel jede Menge Design-Fehler begangen.

Übliche Implementierungen erzeugen zu viele Abhängigkeiten

In der folgenden Abbildung 1 sehen wir eine typische Implementierung, wie sie in der Realität häufig zu finden ist.


Abbildung 1 Typische Implementierung bricht das Single Responsibility Prinzip

Das erste Problem bei der Implementierung in Abbildung 1 besteht darin, dass das Aggregat Customer eine Abhängigkeit zu einer anderen Komponente, nämlich der MailSender Klasse, besitzt. Des Weiteren wird hier das Single Responsibility Principle (SRP) [Martin] verletzt. Die Methode „ChangeAddress“ tut nun etwas, was sie eigentlich nicht vorgibt zu tun. Sie ändert nämlich nicht nur die Adresse, sondern versendet auch eine Mail. Für Client-Code ist das aber nicht erkennbar.

Nun, man könnte die Methode umbenennen in ChangeAddressAndSendAddressChangeMessage o.ä. Diese würde das Problem nicht wirklich lösen. Käme eine weitere Anforderung hinzu, dass der zuständige Sachbearbeiter bei der Adressänderung auch direkt per „Notification“ in seiner Anwendung benachrichtigt werden soll, so würde der Methodenname wieder angepasst werden müssen (z.B. ChangeAddressAndSendAddressChangeMessageAndNotifyDeskOfficer). Methodenname als auch der Code der Methode würden im länger und unleserlicher werden.

In diesem Fall würden sogar noch eine Abhängigkeit zum Aggregat DeskOfficer hinzukommen, um auf diesem die Methode Notify aufzurufen, siehe Abbildung 2. Das Problem von Abhängigkeiten wäre aber ohnehin nicht gelöst. Single Responsibility wird damit auf keinen Fall erreicht.


Abbildung 2 Noch mehr Abhängigkeiten

In Abbildung 3 ist zu sehen, dass das Customer-Aggregat von den anderen beiden Aggregaten direkt abhängig ist. Zwar schreibt Eric Evans [DDD], dass ein anderes Aggregate außerhalb der Aggregatsgrenze referenziert werden kann, falls es nötig ist. Dies führt aber meiner Meinung nach zu großen, unübersichtlichen Objektgraphen und unnötigen Abhängigkeiten. Vaughn Vernon [IDDD] schlägt vor, dass Aggregate ausschließlich durch „Identity References“ –also IDs- auf einander verweisen sollten. D. h. beispielsweise, dass unser Customer Aggregat nur die ID zum verantwortlichen Sachbearbeiter (ResponsibleDeskOfficer) hält, aber keine Objektinstanz. Damit können zudem auch mögliche Performance-Probleme, die bei der Objektinstanzierung auftreten könnten, gelöst werden. Gibt es Änderungen am Customer-Objekt, die auch für den verantwortlichen Sachbearbeiter interessant sind, so muss die betreffende Instanz durch ein Domain Event (Fachereignis) benachrichtigt werden. Um zwei Aggregate, die nichts voneinander wissen, zu synchronisieren, werden demnach Domain Events benötigt. Mit dieser Technik erreicht man die gewünschte Entkoppelung der Komponenten.


Abbildung 3 Das Customer Aggregat muss andere Aggregate referenzieren, um seine Aufgaben zu erfüllen.

Bevor ich weiter auf Domain Events eingehe, hier noch ein weiterer ungünstiger -aber oft verwendeter- Lösungsansatz, bei dem die Aufrufe in eine darüberliegende Dienstklasse, z. B. einem CustomerModifyDomainService verlagert werden, siehe Abbildung 4. Das ist aus meiner Sicht nicht wirklich besser, da das Problem der Abhängigkeiten und Methoden-Benennung nur eine andere Ebene verlagert werden würde.


Abbildung 4 Auch keine Lösung, nur eine Verlagerung des Problems…

Domain Events

Schauen wir durch die DDD-Brille, so können wir aus der o.g. Anforderung ein Domain Event, sprich ein fachliches Ereignis, herauslesen. Im Grunde kann man auf einer abstrakten Ebene sagen: Wenn das Ereignis „Kundenadresse wurde geändert“ eingetreten ist, so muss in der Folge eine Email versendet werden und der Sachbearbeiter direkt im System benachrichtigt werden. Das fachliche Ereignis löst also weitere Aktionen aus. In einem Fachmodell sollte nun ein Ereignis „CustomerAddressChanged“ existieren. Zu beachten ist, dass Domain Events immer in der Vergangenheit formuliert werden. Diese Ereignis tritt immer dann ein, wenn die Adresse eines Kunde verändert wurde. Den Befehl (Command), der das Ereignis auslöst, besitzt das Aggregat Customer in Form der Methode ChangeAddress. Wie andere Aggregate auf dieses Ereignis reagieren müssen, liegt nicht im Verantwortungsbereich des Customer-Aggregates (Seperation of Concerns).

Reden Fachexperten über ihre Fachdomäne, so formulieren sie häufige Ereignisse in der Form „Wenn dies passiert ist, dann muss danach das und das passieren/ informiert werden…“ o.ä.

Domain Events sind Teil der Ubquitious Language [DDD] und müssen sich deshalb auch im Domänenmodell bzw. im Code wiederfinden. Im Gespräch mit den Fachexperten muss herausgefunden werden, wer sich für welches Ereignis interessiert und wie derjenige darauf reagieren muss.

Domain Events sind im Grunde nichts anderes Nachrichten, die zwischen Komponenten eines Systems ausgetauscht werden. Dies entspricht dem Observer Muster [Gamma]. Ein Subjekt veröffentlicht eine Nachricht und ein oder mehrere Beobachter (Observer) abonnieren diesen Nachrichtentyp. Dieses Muster ist auch als Publish-/ Subscribe Muster bekannt. Der Vorteil ist, dass die Komponenten sich nun nicht mehr kennen bzw. miteinander verdrahtet sein müssen.

Für unseren Fall ergibt sich folgende Struktur, Abbildung 5. Die Kommunikation zwischen den Komponenten erfolgt nur noch in Form von Nachrichten. Die Aggregate Root
Customer referenziert nun keine Instanz der Aggregate Root DeskOfficer mehr, sondern hat nur noch eine Identity Reference [IDDD], sprich eine ID, siehe auch Abbildung 6. Ändert sich die Kundenadresse, erfolgt dies durch den Aufruf des ChangeAddress– Commands (Methode) auf einer Customer-Instanz. Dieser Command löst das Domain Event CustomerAddressChanged aus, welches auf einem Message Bus (EventAggregator –Klasse) veröffentlich wird. Alle Aggregate, die diesen Nachrichtentyp abonniert haben, reagieren darauf und können ihre Aufgabe erfüllen. So kann die betreffende DeskOfficer Instanz eine Notification erstellen etc. Für das Versenden der Email würde ich einen spezifischen MessageHandler erstellen, der verantwortlich ist, die richtige Email zu erstellen. Dieser Handler kann dann die MailSender-Komponente referenzieren, die den eigentlichen Versand übernimmt oder die Mail-Nachricht besser zunächst in einer Queue ablegt etc.

 
 


Abbildung 5 Entkoppelte Aggregate


Abbildung 6 Adresse ändern und Domain Event veröffentlichen

In diesem ersten Teil meiner neuen Blogreihe zum Thema „Lose Koppelung durch Messaging“ habe ich eine grundsätzliche Idee geliefert, wie Komponenten entkoppelt werden können. Im nächsten Teil zeige ich, wie die benötigten technischen Komponenten wie EventAggregator und MessageHandler in C# realisiert werden und wie wenig Coding-Aufwand dafür eigentlich notwendig ist.

Als weiterführende Literatur zur Thematik möchte ich vor allem Vaughn Vernons „Implementing Domain-Driven Design“ empfehlen.

 
 

Verwendete Literatur:

[IDDD] Vaughn Vernon, Implementing Domain-Driven Design, 2013, Addison-Wesley

[DDD] Eric Evans, Domain-Driven Design, 2003, Addison-Wesley

[Gamma] Erich Gamma, Design Patterns, 1994, Addison-Wesley

[Martin], Robert C. Martin, Clean Code, 2008, Prentice Hall

Ein Kommentar zu “Lose Koppelung durch Messaging und Domain Events – Teil 1

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s