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

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

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