Gedanken über O/R Mapping und Domain-Driven Design – Teil 1

Warum das Entity Framework für Domain-Driven Design nicht geeignet ist

Ausgangssituation

In der aktuellen Ausgabe des Fachmagazins Visual Studio One ist der vierte Teil meiner Windows Azure Kolumne erschienen. Thematisch geht es um SQL Azure. Im Artikel geht es darum, dass Objektmodell der vorherigen Ausgabe an eine SQL Azure Datenbank anzubinden. Wie schon oft habe ich dabei auch das Thema O/R Mapper andiskutiert. Natürlich habe ich das Thema in den Kontext von Domain-Driven Design gesetzt. Ich habe angekündigt, in einem kritischen Blogbeitrag ausführlicher meine Sichtweise zum Einsatz des Entity Frameworks mit DDD-Ansätzen aufzuzeigen. Den Beitrag habe ich nun schon eine Weile in der Schublade, das heißt als Entwurf in WordPress J. Es ist an der Zeit, diesen nun zu vollenden und vorzustellen.

Häufig wird der Graben zwischen Objektmodell und der relationalen Datenbank als groß beschrieben, meistens eben auch als Problem. Schnell wird dann zu einem O/R Mapper gegriffen und damit sollte das Problem gelöst sein. Doch ist das wirklich so? Ich habe mich in den letzten Jahren in vielen verschiedenen Projekten mit der Thematik auseinandergesetzt und versucht, brauchbare und pragmatische Lösungen zu finden. In vielen Projekten ging es um die Erstellung von Unternehmenslösungen mit umfangreichen, komplexen Geschäftsprozessen. So habe ich vor ca. 5 Jahren begonnen, mich mit Domain-Driven Design zu beschäftigen und Wege der Anwendung dieser Prinzipien in Softwareprojekten im .NET Umfeld zu finden. …und dieser Weg war sehr steinig. Viele Erkenntnisse mussten hart erarbeitet werden. Domain-Driven Design ist inzwischen etwas zum Modewort geworden und wird manchmal leider fälschlich verwendet. Es existieren inzwischen auch sogenannte DDD Light Ansätze, die im Wesentlichen die technischen Aspekte von DDD verwenden.

In Projekten kommt sehr häufig die Frage auf: „Wie mappen wir unser Objektmodell auf die Datenbank?“ Schnell wird dann zu einem O/R Mapper gegriffen und Objekte aus den vorhandenen Datenbanktabellen erzeugt, im Glauben dann ein Domänenmodell zu besitzen. Nur ist das leider nicht der Fall. Entitäten im Sinne von DDD sind etwas anderes als Datenbank-Entitäten. Entitäten im DDD-Ansatz sind Domänenobjekte, die aus Verhalten und Struktur bestehen. Entitäten der Datenbank bestehen nur aus Struktur und natürlich Beziehungen zu anderen Objekten.

Die Mängel des Code First Ansatzes

Mit dem Entity Framework ist es seit einigen Versionen möglich, den Code-First Ansatz zu verfolgen. Dieser erlaubt es, Objekte mit Struktur und Verhalten zu modellieren und daraus Datenbanktabellen erzeugen zu lassen. Ich habe mich schon einige Male in Evaluationen mit diesem Vorgehen beschäftigt und kann mir nicht vorstellen, dass dieser Einsatz für echte Projekte und vor allem in Hinblick auf eine optimale Datenbankstruktur wirklich taugt. Das soll aber nicht Schwerpunkt dieser Ausgabe sein. Ich werde diese Diskussion zu einem späteren Zeitpunkt nochmals aufnehmen.

In dieser Ausgabe möchte ich eher auf die Probleme eingehen, die im Hinblick auf objektorientierte Prinzipien codeseitig entstehen.

Stellen wir uns vor, wir müssten das Domänenobjekt Ente mit verschiedenen Methoden (Verhalten) und Eigenschaften (Struktur) modellieren. Es muss möglich sein, sicher und konsistent neue Objekte zu erzeugen. Die Anforderung besteht darin, dass die Ente (Objektinstanz) eindeutig zu identifizieren ist (ID), einen Namen hat und durch eine Farbe charakterisiert ist. Die Ente soll fliegen können und jeder Flug soll gespeichert werden und über eine Liste der Flüge einsehbar sein. Dies bedeutet, es muss eine Möglichkeit bestehen, die Flüge zu sammeln und zu persistieren. Beim Erstellen einer neuen Objektinstanz soll eine Regel, nämlich die maximale Ausflugsstrecke in Kilometer festgelegt werden. Dieser Wert ist unveränderbar solange die Ente existiert, was bedeutet, dass der Wert nur bei der Erstellung (Anlegen) einer neuen Instanz benötigt wird, persistiert werden soll und dann nur intern in der Klasse selbst verwendet wird, um eine Validierung der Flugstrecke beim Aufruf der Methode „Fliegen“ vorzunehmen. Die Liste der Flüge darf im Übrigen nicht manipulierbar sein.

Daraus ergibt sich folgender Entwurf unter Einhaltung objektorientierter Prinzipien, Abbildung 1. Auch das in meinem Artikel zu Messaging und Entkoppelung erwähnte Geheimnisprinzip soll wieder Anwendung finden.


Abbildung 1 – Objektmodell für die Anforderungen

Die Klasse Ente sieht wie folgt aus, Abbildung 2. Der Konstruktor soll der sicheren Erzeugung neuer Objekte dienen und fungiert quasi als Factory. Die Wiederherstellung bereits persistierter Objekte soll dann das Entity Framework übernehmen. Für den Wert der maximal zulässigen Flugstrecke der Ente, verwendete ich ein readonly Feld, da der Wert nur interne Verwendung findet und nach der Erstellung nicht mehr verändert werden darf. Der Wert muss für Client Code nicht sichtbar sein. In Abbildung 3 ist die Klasse Flug zu sehen, die auch eine Factory-Methode (Konstruktor) nutzt, um konsistente Instanzen zu erzeugen.


Abbildung 2 Die Klasse Ente erfüllt die Anforderungen.


Abbildung 3 Die Klasse Flug.

Soweit so gut. Probieren wir nun den Code aus und schauen, ob sich alles wie gewünscht verhält. Dafür habe ich eine kleine Konsolenanwendung geschrieben, die eine Enteninstanz erstellt und die Ente zwei mal fliegen lässt und dann noch versucht, sie zu überfordern, Abbildung 4 und 5.


Abbildung 4 Aufrufe über ein kleines Programm


Abbildung 5 Die Ausgabe in der Konsole. Anwendung läuft und die Ente lässt sich nicht überfordern

Bis hierher haben wir objektorientiert gearbeitet und saubere Klassen entworfen. Wir haben uns auch nicht um Persistierung gekümmert, da wir dem Code-First Ansatz folgen. Diese Aufgabe übergeben wir nun dem Entity Framework und schauen uns nun an, was passiert. Dafür müssen wir nur eine Klasse erstellen, die DbContext implementiert, siehe Abbildung 6. Danach wird der DataContext in der Konsole verwendet, Abbildung 7.


Abbildung 6 Datenkontext hinzugefügt


Abbildung 7 Den Datenkontext nutzen und das Objekt speichern

Das Entity Framework erstellt die Datenbank und Tabellen im Hintergrund, sofern sie noch nicht vorhanden sind. Schauen wir uns das Ergebnis an, Abbildung 8. Es scheint alles zu funktionieren. Der Code sagt, er hätte eine Ente verspeichert. Prüfen wir das nun in der Datenbank.


Abbildung 8 Die Ausgabe verspricht Gutes

Werfen wir einen Blick in das SQL Server Management Studio. Cool, die Datenbank wurde erstellt. Aber: leider nur unvollständig. Das Feld maximaleFlugStrecke als auch die Flugauflistung fehlen, Abbildung 9.


Abbildung 9 Der Blick ins SQL Server Management Studio zeigt das nicht alle Datenfelder und die Referenz auf die Flüge nicht erstellt wurden

Grund dafür ist, dass das Entity Framework nicht in der Lage ist, Felder als Datenbankspalten zu speichern. Leider gibt es auch kein Attribut dafür. Damit kann ich notfalls leben und mache aus dem Feld maximaleFlugStrecke eine Eigenschaft, so dass die Spalte in der Datenbank erstellt wird.

Das zweite Problem finde ich gravierender. Das Entity Framework kann nicht mit einer Auflistung vom Typ IEnumerable<T> umgehen. Auflistungen müssen vom Typ Collection<T> , damit die Tabellenstruktur richtig erstellt wird. Hier wird aber ein grundlegendes Prinzip der Objektorientierung gebrochen. Aber schauen wir uns zunächst an, wie die Klasse Ente verändert werden muss, damit die Datenbank wie erwartet erstellt wird, Abbildung 10.


Abbildung 10 Notwendige Änderungen an der Klasse für Code First.

Damit das Entity Framework ein Objekt beim Zurücklesen aus der Datenbank wieder instanzieren kann, wird außerdem noch ein parameterloser Konstruktor benötigt. Der wurde in Abbildung 10 ergänzt. Das gefällt mir gar nicht! Denn: Das Erstellen inkonsistenter Objektinstanz ist nun auch grundsätzlich im Client Code möglich. Der Aufruf new Ente() ist nicht zu verhindern. Oje! Die Anforderung „Es muss möglich sein, neue Objekte sicher und konsistent zu erzeugen“ kann damit nicht mehr erfüllt werden.

[Korrektur, 9.8.2013]

Ein Kollege wies mich soeben darauf hin, dass man die für das EF erforderlichen parameterlosen Konstruktoren auch „private“ machen kann. Dann sieht das doch schon besser aus. 🙂 Die inkonsistente Erzeugung neuer Objekte ist damit nicht möglich. (Pluspunkt)

Die private List<Flug> und die öffentliche Auflistung IEnumerable<Flug> wurden entfernt und dafür eine öffentliche Collection<Flug> erstellt. MaximaleFlugStrecke ist nun eine öffentliche Eigenschaft. Die Erstellung der Tabellen in der Datenbank klappt damit reibungslos. Das Ergebnis ist wie erwartet, Abbildung 11.


Abbildung 11 Datenbank wurde wie erwartet erstellt

Das Problem

Welches Problem ergibt sich aber nun aus der geänderten Implementierung aus Abbildung 10?

Änderungen an Daten eines Objektes sollen nur über definierte Schnittstellen möglich sein (Geheimnisprinzip). Die Auflistung der getätigten Flüge darf laut Anforderung nicht manipulierbar sein. Um die Bedürfnisse eines Frameworks zu befriedigen, bin ich gezwungen objektorientierte Prinzipien zu brechen und kann Anforderungen nicht erfüllen. Die Liste der Flüge ist nun für Client-Code (siehe Abbildung 12) ohne weiteres manipulierbar. Ich kann nicht verhindern, dass ein Konsument der Klasse auf der Flüge-Collection Clear() aufruft und die Liste damit leert und speichert.


Abbildung 12 Es entstehen nun ungeahnte und ungewollte Möglichkeiten im Client Code

Öffentliche Auflistungen müssen aus meiner Sicht zwingend vom Typ IEnumerable<T> sein, damit Client-Code die Liste nicht selbstständig ändern kann. Sind Änderungen notwendig, so muss die Klasse entsprechende Methoden anbieten. Nur wegen eines Frameworks dieses Paradigma zu brechen, halte ich für absolut inakzeptabel.

Von soliden, objektorientierten Code haben wir uns mit dem Umbau in Abbildung 10 in jedem Fallen sehr weit entfernt.

Fazit und Ausblick

Ich glaube nicht, dass O/R Mapper im Zusammenhang mit Domain-Driven Design/ Objektorientierung das richtige Mittel der Wahl sind. Man sollte den Sinn eines Einsatz in Abhängigkeit von den Anforderungen genau prüfen. Pauschale Entscheidungen zugunsten oder auch dagegen halte ich für suboptimal. Generell halte ich die Verwendung des Code-First Ansatz dann für geeignet, wenn eine optimierte Datenbankstruktur nicht nötig ist und Anwendungen nur eine geringe Komplexität aufweisen. Ggf. genügen aber für solche Einsatzzwecke auch leichtgewichtigere DataTables. Objektorientierung Out-Of-The-Box sind mit O/R Mappern ohnehin nicht möglich, sondern eher das Gegenteil.

Im zweiten Teil der Reihe werde ich darauf eingehen, warum aus meiner Sicht das Problem die objektorientierte und die relationale Welt in Einklang zu bringen, nicht wirklich, sondern nur in unseren Köpfen, existiert.

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

Windows Azure Kolumne im Fachmagazin Visual Studio One

In der aktuellen Ausgabe der Visual Studio One ist bereits dritte Teil meiner Windows Azure Kolumne zum Thema WCF unter Windows Azure erschienen. Inhaltlich beschäftigt sich die Artikelreihe vor allem mit der Entwicklung von Softwaresystemen (Geschäftsanwendungen), die auf Windows Azure laufen sollen. Grundsätze der Objektorientierung, die Anwendung von Domain-Driven Design und die Erstellung von sauberen Code beziehe ich jeweils in meine Betrachtungen ein.

Einen Artikel Preview findet man hier: http://www.visualstudio1.de/images/Previews/032013-Fritzsche.aspx