Lose Koppelung durch Messaging und Domain Events – Teil 2

Teil 2 – Ein paar Gedanken zu objektorientierte Prinzipien und Implementierung des EventAggregator

Vorheriger Teil – Teil 1 – Aggregate mittels Domain Events entkoppeln und synchronisieren

Einleitung und ein paar Überlegungen

Im ersten Teil der Blogreihe habe ich bereits über Gründe, warum die Entkoppelung von Komponenten so wichtig ist, gesprochen. Anhand einer einfachen Anforderung habe ich gezeigt, wie schnell Komponenten ungünstigerweise miteinander verdrahtet sind. Mit wachsenden Anforderungen steigt die Komplexität einer Anwendung. Ein klar erklärtes Ziel von Domain-Driven Design ist aber die Reduzierung von Komplexität. Ein wirkliches Problem sind Abhängigkeiten. Je komplexer Anforderungen, desto mehr Abhängigkeiten scheint es zu geben. Doch welche Strategie ist die richtige, um Komplexität zu beherrschen. Leider gibt Eric Evans Standardwerk „Domain-Driven Design Tackling Complexity in the Heart of Software“ [DDD] nicht immer ausreichend Antworten dazu. Auch sind die Interpretationen, wie man Domain-Driven Design richtig anwendet, teilweise extrem verschieden. Vielleicht gibt es auch nicht nur den einen richtigen Weg?!

Aus meiner Sicht basiert die Implementierung von Domain-Driven Design auf objektorientierten Prinzipien. Ohne diese ist es nicht möglich, Domänenmodelle zu entwerfen und in lauffähigen Code zu übersetzen. In der Definition von Objektorientierung nach Alan Kay ist alles ein Objekt, wobei Objekte die Instanz einer Klasse sind. Klassen bestehen dabei aus Struktur und Verhalten. Der Zugriff auf die Daten eines Objektes erfolgt über Schnittstellen (damit sind Methoden gemeint), um inkonsistente Objektzustände durch Beeinflussung von außen zu vermeiden. Dahinter steckt im Übrigen das Prinzip der Kapselung (auch Geheimnisprinzip genannt).

Ein typisches Beispiel für die Verletzung des Geheimnisprinzips ist häufig die Verwendung von öffentlichen Settern, die ungewollte Manipulation von Daten eines Objektes zulassen. Im schlimmsten Fall ist es Clientcode möglich, die Identität eines Objekts zu ändern, siehe Abbildung 1.


Abbildung 1 Verletzung des Geheimnisprinzips – Client Code kann ungewollte Manipulierungen am Objekt durchführen

Das Problem, das in Abbildung 1 besteht, ist zum Beispiel folgendes: Client Code bezieht über einen Repository– Aufruf eine Produktinstanz aus der Datenbank. Durch einen Programmierfehler ändert der Client Code die ID des Produkts und gibt die Instanz an das Repository, um es zu speichern. Da es sich nun um eine neue Identität handelt, wird das Objekt in der Datenbank als neue Zeile eingefügt. Klar ist dieses Szenario hypothetisch; es könnte sicher weitere Prüfungen im darunterliegenden Code geben, die dieses Fehlverhalten verhindern etc. Jedoch sollte so ein Problem durch die konsequente Anwendung des Geheimnisprinzips von vornherein vermieden werden. Die Daten dürfen nur kontrolliert über Methoden änderbar sein. Daten die nach der Objektinitialisierung invariant sein müssen, dürfen gar nicht mehr änderbar sein. Diese sind als readonly Felder anzulegen, siehe Abbildung 2. In der Realität existiert Code wie in Abbildung 1 leider viel zu häufig.


Abbildung 2 Korrekte Implementierung – Manipulation der ID ist nicht mehr möglich

Objekte kommunizieren nach Alan Kay immer über Nachrichten miteinander. All diese genannten Designprinzipien als auch die SOLID Prinzipien sind meiner Auffassung nach nötig, um Domain-Driven Design sauber zu implementieren. Ich werde mich in einer der nächsten Blogausgaben nochmals näher damit beschäftigen bzw. darauf eingehen, vor allem warum Domänenobjekte (Entities und Aggregate im Sinne von DDD) immer aus Daten und Verhalten bestehen. Das Erzeugen eines Entitymodells mit Hilfe eines O/R Mappers erzeugt keine Domänenobjekte, sondern lediglich um Datenzugriffsobjekte (DAO, vgl. auch [IDDD] S. 440-441); auch wenn es fälscherlicherweise häufig so gesehen wird, dass es sich bei Datenbanktabellen, die in Klassen übersetzt wurden, um Domänenobjekte handelt. Das ist aber nicht so, da die Klassen nur aus Struktur bestehen und damit nur Datenhalter sind. Domänenwissen ist aber auch Verhalten! Übrigens: Gerade weil O/R Mapper noch CRUD Funktionalität erzeugen, handelt es sich eher um das Table Module Konzept [Fowler], was sich grundsätzlich vom DDD Ansatz unterscheidet.

In der letzten Zeit hat sich in der DDD Community vor allem der Einsatz von Domain Events etabliert. Domain Events spielen eine wichtige Rolle, wenn es darum geht, Komponenten voneinander loszulösen. Nötig sind sie auch, wenn man CQRS anwenden möchte. Auf CQRS werde ich in einem weiteren Beitrag näher eingehen.

Die EventAggregator Implementierung

Nun aber zurück zum Thema Entkoppelung von Aggregaten und der versprochenen Implementierung des EventAggregator. Der EventAggregator soll als zentraler Messagebus innerhalb einer Anwendung agieren. Das Prinzip wie die losgelösten Aggregate miteinander kommunizieren sollen, habe ich in der vorherigen Ausgabe erläutert und schematisch dargestellt.

Der Anspruch dabei ist, dass keine zusätzlichen Systemkomponenten wie beispielsweise eine MSMQ o.ä. benötigt werden, da verteilte Szenarien nicht im Fokus stehen. Vaughn Vernon [IDDD] verwendet in seinen Beispielen eine statische EventPublisher Klasse, um Domain Events zu veröffentlichen. Ich möchte den EventAggregator allerdings gerne aus dem IoC-Container auflösen und per Konstruktorinjektion in betreffende Service- und Repository– Klassen injizieren. Deswegen zunächst die Schnittstellen-Definition. Der EventAggregator soll die beiden Methoden Publish und Subscribe bereitstellen, siehe Abbildung 3. Bei der Schnittstelle IDomainEvent handelt es sich um ein Markerinterface.


Abbildung 3 Die Schnittstellendefinition des EventAggregator

Um eine häufige Frage an dieser Stelle zu beantworten: Dependency Injection ergänzt den DDD Ansatz hervorragend. Alle Komponenten wie Services, Repositories oder Datenbankadapter sollten in einem Container registriert und daraus auflösbar sein. Domänenobjekte entspringen aber niemals dem IoC Container. Für die Instanzierung von Entitäten und Aggregaten sind Repositories zuständig. Benötigt also ein Aggregat eine Instanz des EventAggregator, so reicht das Repository seine injizierte Instanz der Klasse an den Konstruktor des Aggregates weiter.

Bei der Implementierung des EventAggregator habe ich mich stark an dieser Implementierung von Udi Dahan orientiert. Seine Implementierung der DomainEvents-Klasse ist statisch. Ich möchte aber soweit es geht alle Instanzen aus dem IoC-Container beziehen. Einen Grund, warum die Klasse statisch sein sollte, habe ich nicht direkt erkannt, sofern es im gesamten Anwendungskontext möglich ist, die Instanz des EventAggregator als Singleton im IoC-Container zu halten. Wird der Container allerdings immer Per-Call neu erstellt, so sollte bzw. muss der EventAggregator statisch sein.

In meinem Beispeil aus Teil 1 ging es darum, ein Domain Event zu veröffentlichen, wenn die Adresse eines Kunden geändert wurde. So muss zunächst eine Implementierung des KundenAdresseGeändert-Ereignisse erstellt werden, siehe Abbildung 4.


Abbildung 4 CustomerAddressChanged Domain Event Implementierung

Domain Event Objekte sollten nach ihrer Instanzierung unveränderbar sein. Deshalb sollten auch hier wieder private Setter verwendet werden. Domain Events dürfen im Übrigen nicht dazu dienen, ganze Objekte (also z. B. die Customer-Instanz) zu verschicken, sondern nur Identifier oder einfache Datentypen. Benötigt der Empfänger (der Observer) eine Instanz, so muss er sich diese selbst aus seinem Repository besorgen. Das ist wichtig, weil damit vermieden wird, das der Empfänger möglicherweise eine veraltet Instanz erhält. Außerdem müssen die Klassen in verschiedenen Zusammenhängen nicht immer gleich aussehen.

Wurde in der Customer- Klasse nun die Adresse erfolgreich geändert, so wird quasi nur noch ein Domain Event veröffentlicht. Im Sinne des Observer Musters ist die Customer- Klasse das Subjekt. Das Subjekt weiß nicht, wer sich alles dafür interessiert. Die Abhängigkeiten zu der MailSender Komponente und dem Sachbearbeiter (DeskOfficer-Klasse) werden wir mit der Verwendung der Domain Events und dem EventAggregator endlich los. Die Implementierung der Customer- Klasse sieht exemplarisch nun aus wie in Abbildung 5.


Abbildung 5 Customer Klasse besitzt keine Abhängigkeiten mehr zu anderen Aggregaten, nur noch zum EventAggregator

Eine exemplarische Implementierung auf der Empfänger-Seite könnte beispielsweise so aussehen, Abbildung 6.


Abbildung 6 Implementierung des Subscriber Aggregates

Auch der Subscriber, in diesem Fall das DeskOfficer Aggregat, benötigt keine Abhängigkeit zum Versenden. Hier muss ebenfalls die EventAggregator Instanz verfügbar sein. Im Konstruktor wird der Nachrichtentyp registriert. Das erfolgt durch den Aufruf der Methode Subscribe<T> des EventAggregator. Als Parameter muss ein Delegat mit gegeben werden. Damit reagiert die DeskOfficer Instanz auf alle Nachrichten vom Typ CustomerAddressChanged, die auf dem EventAggregator veröffentlicht werden. Das Wissen, was mit der Nachricht zu machen ist, steckt in der OnCustomerAddressChanged-Methode des DeskOfficer Objektes. Das Geheimnisprinzip bleibt gewahrt. Kein anderes Objekt entscheidet, wie sich der DeskOfficer zu verhalten hat, wenn das Fachereignis „Kunde Adresse wurde geändert“ eingetreten ist.

Schauen wir uns zum Schluss noch die Implementierung des EventAggregator selbst an. Ich habe mich in diesem Beispiel auf die Verarbeitung der registrierten Delegate beschränkt, siehe Abbildung 7. Deswegen habe ich dieses Bespiel mit „einfache“ Implementierung betitelt. In der nächsten Ausgabe werde ich den EventAggregator so erweitern, dass er auch in der Lage ist, MessageHandler Implementierungen aus einem Unity-Container aufzulösen.


Abbildung 7 Einfache EventAggregator Implementierung

Ich hoffe, die ersten beiden Teile konnte die Vorteile von Messaging und der Entkoppelung von Komponenten nachvollziehbar aufzeigen. Über Feedbacks und Kommentare freue ich mich natürlich. Was haltet ihr von diesem Vorgehen?

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

[Fowler], Martin Fowler, Patterns of Enterprise Application Architecture, 2003

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