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.

14 Kommentare zu „Gedanken über O/R Mapping und Domain-Driven Design – Teil 1

  1. Eine Korrektur habe ich schon mal. Ein Kollege teilte mir gerade mit, dass man die für das EF erforderlichen Konstruktoren auch „private“ machen kann. Dann sieht das doch schon besser aus. Die inkonsistente Erzeugung neuer Objekte ist damit nicht möglich.
    Hat vielleicht auch jemand eine Lösung, dass man keine öffentlichen Auflistungen vom Typ Collection nutzen muss, sondern IEnumerable verwenden kann?

  2. Ist zwar schon etwas her, aber…
    Zum Thema IEnumerable.

    Weg, den ich mit EF6 finden konnte (nicht mit früheren Versionen versucht):
    – von Ente muss erbbar sein
    – Parameterloser Konstruktor von Ente muss mind. protected sein
    – fluege von private readonly List umwandeln nach protected virtual ICollection
    – fluege Mappen wie hier beschrieben http://www.codewrecks.com/blog/index.php/2011/03/21/mapping-private-properties-with-ef-4-1-rc-and-fluent-mapping/

    –>

    // Änderungen an Klasse Ente

    public class Ente
    {
    protected virtual ICollection fluege { get; set; }
    public IEnumerable Fluege { get { return fluege; } }

    protected Ente()
    {
    Id = Guid.NewGuid();
    fluege = new List();
    }

    […]
    }

    // DataContext: Mapping mit fluent API

    class DataContext:DbContext
    {
    public DbSet Enten { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    modelBuilder.Entity().HasKey(x=> x.ID).ToTable(„Fluege“);
    modelBuilder.Entity().HasKey(x => x.Id).ToTable(„Enten“);
    modelBuilder.Entity()
    .HasMany(„fluege“)
    .WithRequired().Map(x => x.MapKey(„Ente_RefID“));
    }
    }

    // Für’s mapping der privaten Collection benötigte Extension-Methode

    public static System.Data.Entity.ModelConfiguration.Configuration.ManyNavigationPropertyConfiguration HasMany(this System.Data.Entity.ModelConfiguration.EntityTypeConfiguration mapper, String propertyName)
    where T : class
    where U : class
    {
    Type type = typeof(T);
    ParameterExpression arg = Expression.Parameter(type, „x“);
    Expression expr = arg;
    PropertyInfo pi = type.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

    expr = Expression.Property(expr, pi);
    LambdaExpression lambda = Expression.Lambda(expr, arg);
    Expression<Func<T, ICollection>> expression = (Expression<Func<T, ICollection>>)lambda;

    return mapper.HasMany(expression);
    }

  3. Sie haben in diesem Post Entity Framework und speziell Code First ja ganz schön abgekanzelt. Ich bin sicherlich kein Fanboy von Entity Framework. Aber diese Kritik hat es sicher nicht verdient.

    1) Ihre Erwartungen vs. Code First Realität

    Sie haben scheinbar die Erwartung, dass Entity Framework mittels Code First beliebige Objektmodelle speichern kann, die ohne Bezug zur physischen Speicherung und auch jedes Mapping erstellt wurden.

    Wie soll das funktionieren? Gar nicht.

    Was Microsoft mit Code First liefert, ist eine Möglichkeit für Coder, das Entity-Modell in ihrem geliebten VS Code Editor in C# reinzuklimpern.

    Ich kann solchen Wünschen eigentlich wenig abgewinnen, aber Microsoft ist in dieser Hinsicht ein durchaus kundenorientiertes Unternehmen und liefert, was sich viele (fehlgeleitete) Entwickler wünschen.

    Fehlgeleitet deshalb, weil Klassen in C# von Haus aus nicht die Semantik eines vollständigen Entity-Modells abbilden. Man denke z.B. an inverse Mapping von Collections, Multiplizität etc.

    Was Sie selbst Erwarten ist einfach nicht implementierbar, da wären wir auch schon bei 2.

    2) Die Illusion von Persistence Ignorance (PI)

    Zugegeben, auf den ersten Blick sieht es so aus, als könnte man mit Code First einfach beliebige Objekte speichern, aber wie Sie so schön mitbekommen haben, muss es schon eine ICollection sein, die man da zuweist.

    Wenn man sich intensiver mit EF beschäftigt, wird man feststellen, dass EF dynamisch Subclasses emittiert und den Collections EntitySet Instanzen zuweist. EntitySet sind die selben Klassen, die im Model-First Ansatz von Visual Studio zum Einsatz kommen. Code First ist eben nur ein Aufsatz auf das traditionelle EF. Das hätte Microsoft etwas flexibler handhaben können, zugegeben, aber selbst wenn sie das bei den Collections hinbekommen hätten, für das Change Tracking ist Microsoft darauf angewiesen, dass Properties als virtual deklariert werden, sodass die generierten Klassen diese überschreiben können. Das ist ein Grundproblem von PI, selbst der vor 10 Jahren top-moderne Ansatz von NHibernate sah dies vor.

    Schlussendlich ist Persistence Ignorance ein Hirngespinst, welches gar nicht implementierbar ist. Wenn man über den Makel von virtual Properties noch hinwegsehen mag, in realen Projekten wird man sicher einmal SessionSave oder Transaktionssteuerung nutzen müssen und die ist sicher Technologie-spezifisch! Ich denke es gibt keinen Code einer realen Anwendung, der zwischen Entity Framework und NHibernate binär kompatibel wäre.

    Ich finde es überhaupt nicht schlimm, dass Entity Framework keine Persistence Ignorance bietet, ganz im Gegenteil. Es bietet eine sehr rationale Möglichkeit von O/R Mapping zu profitieren.

    Für 70-90% des Codes kann man von der Einfachheit des O/R Mappings profitieren, für Performance-sensitive Teile kann man Stored Procedures sehr komfortabel einbinden. Diese ganzen Layers wie z.B. NHibernates HQL sind einfach unnötig. Vor 10 Jahren war es vielleicht nicht so klar. Aber heute: In der Regel steht an der anderen Seite des ORMs SQL Server und der kann nun mal Stored Procedures. Eine 100% Flexibilität gegenüber der Datenbank ist meistens einfach nicht nötig.

    3) Domain Driven Design

    Zu guter Letzt finde ich Ihre Euphorie gegenüber Domain Driven Design nur bedingt sinnvoll. Wenn Sie „Domain Driven“ darin sehen, eine Domäne zu strukturieren, mit klaren Namen und Aufgaben zu versehen, dann d’accord – wunderbar! Praktisch geht DDD oft auch so: einfach nur jegliche Logik, die etwas mit Entität X zu tun hat, in dessen Klasse zu klatschen. Nun das ist schon einmal kommunikative Kohäsion. So kommt man auch durchaus eine Weile gut zurecht, sagen wir mal für Systeme mit bis zu 50.000 LOC oder vielleicht 100 Entitäten. Oberhalb dieser Grenze ist diese primitive Auslegung von DDD jedoch tödlich. Die gute alte funktionale Zerlegung von Systemen wird dann gerne außen vor gelassen. Verantwortlichkeiten sind dann wild zerstreut. Und nein, das ist keine Objektorientierung, das ist schlicht Mist.

    Ich sehe übrigens überhaupt keinen Unterschied aus der DDD Sicht das Verhalten in eine partial class zu packen wie beim Designer-Ansatz oder eben in Code-First in eine gemeinsame Datei. DDD ist in beiden Fällen gleich gut oder schlecht implementiert.

    Entity Framework ist aus meiner Sicht vielleicht wie Windows 98. Es funktioniert noch nicht alles reibungslos, aber Microsoft wird es kontinuierlich weiterentwickeln und es wird tatsächlich besser werden. Das ist die große Stärke von Entity Framework. Wenn es nicht gut mit DDD zusammenpasst, würde ich eher den Fehler in der Interpretation von DDD suchen, als bei Entity Framework.

    1. Hallo Andreas, vielen Dank für Ihren Kommentar zu meinem Blogbeitrag.
      Im Grunde ging es nicht darum, das EF in irgendeiner Form madig zu machen. Ich gebe Ihnen in jedem Falle recht, dass Microsoft hier sehr kunden- und bedarfsorientiert arbeitet. Sicher gibt es auch eine Menge Anwendungsfälle, in denen es der richtige Weg ist, einen O/R Mapper zu verwenden. Die Probleme entstehen allerdings meist später mit zunehmender Komplexität. Meine Probleme habe ich vor allem mit der Auffassung, dass ein Objektmodell, welches aus einem relationalen Datenmodell generiert wurde, mit einem Domänenmodell gleich gesetzt wird. Leider existiert diese Auffassung aber häufig (auch in Tutorials etc.).
      Ich glaube allerdings: Projiziert man objekt-relationale Strukturen in Objekte, handelt es sich um in ein Datenmodell und nicht Domänenmodell. Domänenmodelle sind aber was ganz anderes als objekt-relationale Datenstrukturen, die auf Objekte gemappt wurden. Implementierungen, die mit O/R Mappern durchgeführt wurden, folgen dann häufig dem Anti-Pattern „Anemic Domain Model“ (Fowler), wobei eine Trennung zwischen Struktur und Verhalten stattfindet. Das „Anemic Domain Model“ ist hauptsächlich durch O/R Mapper entstanden (Vernon). In diesem Ansatz gibt es Data Transfer Objekte (DTO) und Komponenten, die Daten manipulieren (damit das funktioniert, gibt es public Setter). Typisch für diesen Ansatz ist vor allem, dass ein Datenobjekt aus der Datenbank geholt, verändert und zurück gespeichert wird (CRUD).
      Das ist aber kein DDD, sondern eine Active Record Pattern (Fowler).
      Die Verwendung eines O/R Mappers in Verbindung mit CRUD ist demnach genau das Gegenteil zu DDD.
      Das ist eigentlich nicht weiter schlimm. Ich wollte die Tatsache nur im Artikel klarstellen und auf den Fakt hinweisen, dass die weder was mit Objektorientierung noch DDD zu tun hat. Wichtig ist bei allen Softwareprojekte, dass man weder pauschal immer den einen oder anderen Ansatz verfolgt, sondern genau prüft, was für die konkrete Aufgabenstellung der beste Weg ist. Für Geschäftsanwendungen mit viel Logik ist DDD oft eine gute Wahl. Jedoch muss man sich mit dem DDD Ansatz sehr intensiv auseinandersetzen. DDD ist mehr als Ubiquitious Language oder das Verbinden von Struktur und Verhalten in einer Klasse. Essentiell ist die Erarbeitung ein oder mehrere Bounded Context und der Context Map sowie das Entwerfen von Aggregaten. Ebenso muss das Domänenmodell losgelöst von technischen Aspekten und testbar sein. Im besten Falle separiert man noch Schreiben und Lesen (CQRS, Event Sourcing) usw. Tödlich ist in jedem Falle eine zu primitive Auslegung von DDD- wie Sie bereits richtig geschrieben haben! Einfach nur Logik in eine Klasse zu „klatschen“ ist eben nicht DDD!
      Hat man DDD jedoch einmal richtig verstanden, dann erleichtert dieser Ansatz die Implementierung ungemein. Technische Komponenten müssen nur einmal implementiert werden und man kann sich auf die fachlichen Themen konzertieren. Und: Es gibt einen konkreten Plan, wie die Software zu bauen ist, der allen im Team klar ist. Meiner Erfahrung nach, spart man mit CQRS und Event Sourcing am Ende viel Zeit und Nerven.
      Interessant finde ich in Ihrem Kommentar die Aussage, dass das „einfach Logik in eine Klasse klatschen“ bis 50.000 LOC gut geht?! Mich würde interessieren, woher diese Zahl stammt? Ist das ein Erfahrungswert?
      Beste Grüße, Rico

      1. Kann auch gut sein, dass ich DDD falsch interpretiere. Ich war jedoch vor einigen Jahren recht angetan von den Ansätzen, wie z.B. von Jimmy Nielson beschrieben: http://www.amazon.de/Applying-Domain-Driven-Design-Patterns-Examples/dp/0321268202. Allerdings hat das für mich in Sackgassen geführt, eben ab 50.000 LOC, vielleicht auch ab 25.000. Sie wissen schon wie ich es meine. SAP oder MS Navision könnte man damit sicher nicht implementieren. Ein Tool für Provisionsabrechnung dagegen schon.

        Mal ein Beispiel was ich als DDD interpretiere: Kürzlich habe ich ein Synchronisationsframework für Entity Framework entwickelt. Kurz gesagt werden Änderungen eines EF SaveChanges geloggt und dann in anderen Clients synchronisiert. (Hier finden Sie einen kleinen Funktions & Design-Überblick: http://kleffels-software-blog.de/?p=1192). Natürlich gibt es Entites, die diese Änderungen/Logs repräsentieren, aber die Logik die beim Loggen und Synchronisieren passiert ist vielfältig und im Sinne der Domäne als zwei separate Bereiche zu sehen. Die Entities sind wie sie geschrieben haben „Anemic“ und sie besitzen public getter und setter. Ein Geheimnisprinzip gibt es dadurch tatsächlich nicht. Jedoch ist durch die Kohäsion, die durch die funktionale Zerlegung (in Loggen und Synchronisierung) entsteht,höher zu bewerten als die kommunikative: http://de.wikipedia.org/wiki/Kohäsion_(Informatik)

        Je größer ein System wird, desto schwerer wird die funktionale Zerlegung. Mit dem Nielson Mindset kommt man leicht zur kommunikativen Kohäsion, aber schwer zu funktionalen.

        Übrigens: gleichzeitige kommunikative und funktionale Kohäsion ist wahrscheinlich das was Sie anstreben (= Informationale Kohäsion). Aus meiner Sicht ist dies jedoch nicht häufig erreichbar, in kleinen Anwendungen leichter als in großen.

        CQRS & Co. würde ich aus der Betrachtung völlig außen vor lassen. Das ist für Systeme nützlich, die extreme Last und Performance-Anforderungen haben und trotz diverser Daten-Redundanzen leicht verständlich und wartbar bleiben sollen.

  4. Hallo Andreas,
    ich kenne das Werk von Jimmy Nielson und habe ebenfalls vor einigen Jahren auf Basis seiner Interpretation von DDD Software implementiert. Allerdings bin ich mit diesem Ansatz und den Ergebnissen auch wenig zufrieden und habe weiter nach anderen Ansätzen gesucht, um DDD anzuwenden bzw. zu implementieren.
    Das Problem bei der Nielson Interpretation ist vor allem, dass er keine Domain Events nutzt. Dadurch ist zum eine Koppelung der Aggregate erforderlich und zum anderen sind viele prozedurale Aufrufe notwendig. Zudem fehlt mir die Unterscheidung zwischen Application und Domain Services, die unterschiedliche Zuständigkeiten besitzen. Als Lektüre zu diesem Thema kann ich nur Vaughn Vernon „Implementing Domain-Driven Design“ empfehlen, http://www.amazon.de/Implementing-Domain-Driven-Design-Vaughn-Vernon/dp/0321834577
    Bei Nielson stört mich, dass die Aggregate miteinander verkoppelt werden. Zum Beispiel kann in seinem Beispiel die „Order“ nur instanziiert werden, wenn man auch im Besitz einer „Customer“ Instanz ist. Das ist aus meiner Sicht ungünstig, da es damit möglich ist, einer „Order“ den falschen „Customer“ unterzujubeln (klar, das kann im Code auch wieder verhindern…). Außerdem erschwert dieser Ansatz das Testen. Besser ist es, hier mit Domain Events zu arbeiten.
    Nielson beschreibt zwar auch das Bounded Context Pattern, weist aber aus meiner Sicht zu wenig auf die essenzielle Bedeutung hin. Für große Systeme ist es wichtig, dass diese in verschiedene Kontexte zerlegt werden. Die Zerlegung einer Domäne in Kontexte ermöglicht beispielsweise das parallele, losgelöste Arbeiten verschiedener Teams an einem Projekt. ERP-Systeme wie SAP und Co. sind aus meiner Sicht riesige Monolithen, die vor allem prozeduralen Programmiermustern folgen. Dass bei diesen Systemen Customizing und Maintenance aufwändig und extrem teuer sind, ist hinlänglich bekannt. Deshalb glaube ich vielmehr, dass man bei der Entwicklung derartiger Systeme gut daran täte, DDD richtig anzuwenden. Die verschiedenen Aspekte solcher Systeme eignen sich hervorragend, um die Domäne in verschiedene Kontexte mit jeweils eigener Fachsprache (Ubiquitious Language) zu zerlegen, z. B. Rechnungen, Kunden, Lieferanten, Lager etc.
    Zum Thema CQRS/ES: Aus meiner Sicht ist diese Auslegung von DDD nicht nur für Systeme mit hohen Performanceanforderungen geeignet, sondern generell ein hervorragender Ansatz, Software sauber und übersichtlich zu strukturieren. Die Trennung von Schreiben (Commands) und Lesen (Query) geht schon auf Bertrand Meyer (CQS) zurück und wurde durch Greg Young als CQRS Muster bekannt, bei dem es im Grunde eigentlich auch nur um die Trennung von Zuständigkeiten geht. Das Gute daran ist, dass man sich mit diesem Ansatz weit von relationalen Datenmodellen löst und im Zusammenspiel mit Event Sourcing echte Objektmodelle ohne Rücksicht auf Datenstrukturen entwerfen kann.
    Das häufige „Aber“, dass CQRS/ES alles Mehraufwand sei, kann ich nicht bestätigen. Ich bin sogar überzeugt, dass sich der Implementierungsaufwand verringert.
    Ihr Beispiel zum Thema Synchronisation mit dem EF habe ich mir angeschaut. Mir gefällt Ihr Vorgehen sehr gut. Aus Sicht der Objektorientierung ist es natürlich sehr löblich, dass Logging und Synchronisation voneinander getrennt sind. Vor kurzem hatte ich in einem Kundenprojekt eine ähnliche Problemstellung allerdings ohne EF zu lösen. Das Monitoring (Subscriber) habe ich dabei mit der Sync-Komponente (Publisher) über Domain Events kommunizieren lassen, so dass beide Komponenten komplett losgelöst sind.
    Cheers, Rico

    1. Die Diskussion geht zwar stark vom Artikel weg, ist aber seht interessant.

      Ich denke, dass Entity Framework für DDD à la Jimmy Nielson hervorragend geeignet ist, wenn man zu kleineren Kompromissen bereit ist und das Entity Model nicht persistent ignorant erstellt. Ich denke, dass (noch) die Hauptzahl der Entwickler DDD so versteht. Und für viele Anwendungsfälle ist das auch gangbar. Wichtig ist mir auch, dass zwischen Code-First und Model-first (bzw. reverse engineered Model via Database-first) aus meiner Sicht kein Unterschied für die Eignung von EF besteht.

      Ob Entity Framework für das CQRS / Event Sourcing Pattern gut geeignet ist, ist fraglich. Für das WriteModel und den Event-Teil wohl eher nicht. Für das ReadModel durchaus, wie sie ja auch geschrieben haben, wobei ich dann nicht verstehe, warum es zwingend aenemic sein soll. Es kann ja trotzdem (lesendes) Verhalten kapseln. Ob CQRS generell ein guter Einsatz ist, kann ich nicht einschätzen. Ich habe jedoch einmal in einem Talk über eine Hochlast-Plattform (ich glaube es war mobile.de) über die Einsatzmöglichkeiten gelauscht und dafür ist es auf jeden Fall eine grandiose Option. Z.B. wenn die ReadModel dann bereits partielles HTML ist und somit einen riesigen Performance-Boost leistet. So etwas lässt sich anders nur schwer wartbar entwickeln.

      Es führen ja bekanntlich viele Wege nach Rom. Um Komplexität zu handhaben, habe ich in der Vergangenheit das Datenmodell monolithisch entworfen, denn ich denke Normalisierung ist weiterhin ein hohes Gut, und dann modular Verhalten hinzugefügt, d.h. nicht alles in die Haupt-Entities. Auch Bounded-Context mit mehreren kleineren Datenbanken kenne ich. Das hat durchaus Vorteile, aber eben auch Nachteile für die Integration. Lustigerweise denke ich, dass Bounded Context gerade für ERP nicht sinnvoll wäre, da hier das integrierte Datenmodell ja mal das Hauptdesign-Ziel war. Microsoft’s ERP kann ich nicht einschätzen, dass SAP technologisch nicht gerade auf dem aktuellsten technologischen Stand ist, darüber brauchen Sie mit mir nicht diskutieren, da ist der monolithische Ansatz sicher das kleinste Problem 🙂

      Was ich recht interessant fand, dass Sie auch einmal dieses Synchronisations-Problem gelöst haben. Hier ist es tatsächlich so, dass das eigentliche Event vom Lesemodel abstrahierbar ist. Z.B. wäre vorstellbar bei Entity Framework die in der Session geänderten Daten in einem JSON oder XML Aggregat zusammenzufassen, was dann eine Optimierung des Read-Model wäre (Reduktion Abfragen und JOINS) und ein konkreter Nutzen für CQRS. Jedoch kommt man hier auch schnell zu falsch Abstraktionen. Das Write-Model muss trotzdem erst einmal wissen, welche Daten überhaupt für das Read-Model relevant sind. Beispielsweise analysiere ich in meinem Sync-Framework beim Schreiben die Beziehungen und lege für die spätere Synchronisation die geänderten (inversen) Collections in der Datenbank ab, sodass beim Synchronisieren nur ein einfaches Abarbeiten nötig ist. Insoweit besteht eine inhaltliche Abhängigkeit zwischen Schreiben und Lesen, sodass man die Teile nicht völlig voneinander losgelöst sehen kann.
      Insgesamt sehe ich in dem Sync-Problem aber auch einen Sonderfall. Es sind temporäre Daten, keine langlebigen Geschäftsobjekte im Sinne der Hauptanwendung ohne zyklische Verweise und dazu noch extrem wenig Entitäten. Ob man von so einem Problem auf eine große Anwendung schließen kann, weiß ich nicht.

  5. »Öffentliche Auflistungen müssen aus meiner Sicht zwingend vom Typ IEnumerable sein, damit Client-Code die Liste nicht selbstständig ändern kann.«

    Das ist aber nicht ausreichend, um das Ziel zu erreichen, denn man kann noch immer folgendes tun.

    ((List)ente.Fluege).Clear(); // Oooops!

    Will man das Ziel wirklich erreichen, muss der Getter eine schreibgeschützte Variante der Collection zurück geben, zum Beispiel als eine ReadonlyCollection.

    public IEnumerable Fluege { get { return new ReadonlyCollection(this.fluege); } }

    Wenn es nur um den eigenen Code geht, ist das unter Umständen schon übertrieben, wird aber auf jeden Fall relevant, wenn man Code von dritten interagieren muss, zum Beispiel in Bibliotheken oder Systemen mit Plugins. Dann gibt es natürlich noch immer den Weg über Reflection direkt auf private Felder zu zugreifen, was man dann auch noch unterbinden muss, wenn an es mit potentiell bösartigem Code dritte zu tun hat.

    Unterstützung für das Mappen von privaten Feldern ist zumindest in Planung, so dass readonly Properties und Collections besser unterstützt werden – siehe Entity Framework Design Meeting Notes vom 2013.02.20 [1]. Die beschriebenen Probleme sind aber keine prinzipiellen Probleme zwischen Domain-Driven Design und O/R-Mapping sondern nur Symptome der nicht perfekten Implementierung des O/R-Mappers. Wird dieser in die entsprechende Richtung weiter entwickelt, lösen sich auch die Probleme in Wohlgefallen auf.

    [1] http://entityframework.codeplex.com/wikipage?title=Design%20Meeting%20Notes%20-%20February%2020%2C%202013

    1. Daniel, da hast du natürlich vollkommen recht. Um zu verhindern, dass der Aufrufer die Liste verändert, muss natürlich eine Kopie zurückgegeben werden und nicht die Originalliste.
      Zum Thema O/R Mapper und DDD: Ich bin da etwas anderer Meinung und glaube eher, dass die Verwendung von O/R Mappern meist zum Anti-Pattern Anemic Domain Model führen als zu einer DDD Implementierung. Bei einem CQRS Ansatz spricht allerdings nichts gegen die Verwendung eines O/R Mappers für die DTOs des Read Models. Aggregate sollte durch einen EventStream instanziiert werden. Damit ist man ohnehin unabhängig von relationalen Datenmodellen. Aus meiner Sicht gibt es keinen Grund, dass die Objektstruktur der Struktur der Datenbank entspricht oder ähnlich ist (außer natürlich bei Read Model, das ist aber Read-Only und verfolgt genau den Zweck). Da gibt es deutlich bessere Wege.

  6. Das Problem des Ansatzes, das Entity Framework zur Persistierung des Domain Models im DDD-Sinne zu benutzen, liegt meines Erachtens darin, dass hier das Domain Model mit dem Persistence Model vermischt wird. Das Entity-Framework für eine mögliche Implementierung der Persistenzschicht zu verwenden, ist sicher sinnvoll und effizient. Ein anderes Persistenzmodell könnte eine Dokumentdatenbank oder einen Eventstore nutzen.

    Essentiell ist jedoch, dass das Domain Model keinerlei Abhängigkeiten zum Persistenzmodell haben darf. Dadurch lässt es sich einfacher testen, erweitern und an sich ändernde Anforderungen anpassen. Unabhängig davon kann sich das Persistenzmodell ändern, um etwa durch Denormalisierung die Performance zu erhöhen, oder um durch Redundanz eine höhere Ausfallsicherheit zu gewährleisten.
    Zwar hat man durch ein Domain Model, dass agnostisch gegenüber seiner eigenen Persistenz ist, initial einen etwas höheren Entwicklungsaufwand, dieser trägt jedoch schon bei kleinen Änderungen Früchte trägt. Außerdem kann die Persistenztechnologie problemlos ausgetauscht werden, ohne das Domain Model anzupassen.

    Fazit: Die Frage „Wie mappen wir unser Objektmodell auf die Datenbank?“ ist meiner Meinung nach für jede nicht-triviale Anwendung mit „Gar nicht!“ zu beantworten.

    1. Schön, dass es auch nach über zwei Jahren noch Reaktionen auf den Artikel gibt. 😉 Das freut mich!
      Mathias, ich sehe das genauso! Im Grunde ist das ja auch die Kernaussage des Artikels. („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.“)
      Datenmodelle und Domänenmodelle sind zwei unterschiedliche Dinge. Für Domänenobjekte stellt sich die Frage eigentlich gar nicht, denn es muss in jedem Falle persistenzunabhängig bleiben. Um dies zu realisieren, eignet sich meiner Erfahrung nach CQRS/ES.
      Meine Kritik liegt im Nachhinein betrachtet wohl vor allem darin, dass Entwicklern oft in Tutorials etc. suggeriert wird, dass mit der Generierung von Modellen mittels eines O/R Mappers ein Domänenmodell vorhanden ist. Diese Denkweise kritisiert im Übrigen auch Vaughn Vernon in Implementing Domain Driven Design.
      Für Lösungen, die nicht zwingend ein Domänenmodell benötigen und auf CRUD-Prinzipien aufbauen, ist der Code-First Ansatz eine wirklich gute Lösung, nur sollte das Repräsentation des Datenmodells im Code nicht als Domänemodell bezeichnet werden.
      Für die Erstellung/Erarbeitung von Read Models bei der Anwendung von CQRS/ES hat sich Code First mit dem EF in der Praxis ebenfalls sehr bewährt. Das habe ich auch in einem meiner Visual Studio One Artikel 2015 beschrieben.

      1. Mein Kommentar wollte auch sagen, dass sich das Entity Framework sehr wohl dazu eignet, die Persistenzschicht einer DDD-Anwendung zu bilden, als eine Alternative zu ES. Wichtig ist, dass diese Technologien in der Persistenzschicht bleiben, und nicht – wie leider zu häufig – in das Domain Model Einzug halten. Die Schnittstelle zwischen den beiden Layern wird in DDD im Allgemeinen mittels des Repository-Patterns implementiert, was im Endeffekt CRUD für Aggregate ist. Dadurch leidet meist die Reichhaltigkeit des Domain Models, da zu viel Wert darauf gelegt wird, dass es einfach persistierbar ist.
        Ich habe eine Alternative gefunden, die den Gedanken von DDD meines Erachtens besser widerspiegelt, da hier nicht Aggregate, sondern Änderungen an Aggregaten persistiert werden. (Interfaces im Domain Layer, Implementierungen im Persistence Layer):

        Ein Aggregate Loader mit einer Methode
        TAggregate RestoreAggregate(Guid aggregateId)
        und eine transaktionale Speicherkomponente mit der Methode
        void PersistTransaction(IEnumerable events)

        Damit kann die Persistenz gleichermaßen durch einen O/R-Mapper, eine ES-Infrastruktur, REST-Services, etc. geschehen, ohne das Domain Model anzupassen. In der Schnittstelle werden nur Konzepte verwendet, die in DDD-Domain Models ohnehin vorhanden sind. Das schöne daran ist, dass ich das Domain Model testen kann ohne DDD zu verlassen und z.B. ein DbSet oder einen EventStore zu mocken.

        CQRS/ES hingegen macht ja gerade abhängig von der Persistenzimplementierung, eben mit ES. Für mich ist CQRS eine Technik des Domain Models, ES hingegen eine Implementierung des Persistence Models. CQRS beschreibt ja nur, dass ich nicht ein Domain Model, sondern getrennte Models für Commands und Queries habe. Diese kann ich dann wiederum verschieden persistieren.

        Eine Möglichkeit ist der von dir beschriebene Ansatz (Query Model/EF + Command Model/ES), eine andere z.B. (Query Model/DocumentDB + CommandModel/REST-Services). Die Persistenztechnologie ist also austauschbar, das Domain Model nicht. Darum sollte in der Entwicklung der Schwerpunkt auf dem Design des Domain Models liegen. Eine passende Persistenzschicht kann am Ende des Projektes, wenn sich das Domain Model bereits etwas stabilisiert hat, implementiert werden.

  7. Grundsätzlich kann ich deinen Aussagen schon zustimmen.
    Verwirrend finde ich nur, dass du mit deiner Alternative („Ich habe eine Alternative gefunden, die den Gedanken von DDD meines Erachtens besser widerspiegelt, da hier nicht Aggregate, sondern Änderungen an Aggregaten persistiert werden. (Interfaces im Domain Layer, Implementierungen im Persistence Layer)… „) eigentlich exakt das Prinzip Event Sourcing (ES) und nicht eine Alternative beschreibst, was wir ja auch genau so bereits praktiziert haben. Das Speichern von Events, also Ereignissen, die den Zustand eines Aggregates verändert haben, anstelle den Objektgraph im letzten Zustand zu persistieren, ist meiner Meinung nach die richtige Art und Weise, Aggregate unabhängig von Persistenz zu bekommen.
    Event Sourcing schafft eben keine Abhängigkeit zu Persistenz, einer Technologie oder Datenbank. Aggregate kennen nur ihre Events, die ein Teil des Modells sind, und werden durch eine Auflistung dieser instanziiert.
    Aus diesem Grunde sind die Domänemodelle eben auch problemlos testbar. Genau das haben wir ja mit einem erfolgreich realisierten Projekt auch nachgewiesen. Würden wir da die Persistenztechnologie für die Speicherung der Events (also den EventStore) tauschen, hätte das keine Auswirkung auf das Domain Model.

    Konkrete Technologien für die Persistierung von Query Models und Command Models sind natürlich frei wählbar. Gerade für die Persistierung der Events sind aus meiner Sicht NoSQL Datenbanken eine gute Wahl.

    1. Hallo zusammen,

      das Thema „Wie schaffe ich die Brücke zwischen dem relationalen Datanbankmodell und dem Objektmodell“ beschäftigt mich auch schon seit fast 20 Jahren 🙂

      Da Objektorientierung und Relationen konzeptionell ganz unterschiedlich sind, ist das natürlich keine einfache Aufgabe und ja, so wie von Euch beschrieben auch nicht lösbar. Das ist so ähnlich wie der Ausspruch Äpfel mit Birnen vergleichen.

      Persistance Ignorance kann nicht funktioneren, weil natürlich muss sich die Abhängigkeit zur Datenbank irgendwo niederschlagen.

      Mein Ansatz ist ein ganz Anderer: Das objektorientierte Datenmodell (Domänenmodell) ist doch – wie auch hier so ähnlich erwähnt – ein rein gedankliches Modell mit keinerlei Bezug zu einer technischen Umsetzung. Jedoch ist dieses Modell sehr wichtig, da es der fachlichen Realität näher kommt als alle anderen Ansätze. Und alleine das war mal der GEdanke, der die Entwickler der Objektorientierung geleitet hat. Das heißt, das Konzept der Objektorientierung ist eigentlich ein abstrakte Konzept. D.h. es existieren die gedachten Klassen, deren öffentliche oder geheimen Attribute, das Verhalten etc. Wirklich objektorientiert programmieren kann ich nur im luftleeren Raum, d.h. ohne Persistierung, ohne Netzwerkverkehr und ohne Darstellung auf der GUI. D.h. ich darf nur Java oder C# pur verwenden. D.h. die bekannten Datentypen, Collections etc. als Typen von Attributen und Methoden eben um deren Verhalten zu implementieren. Kein WPF, kein WCF, keine Datenbank (auch nicht EF) darf vorkommen.

      Da wir heute in der modernen Softwareentwicklung von Schichten sprechen, die optimal voneinander getrennt sein sollen, sehe ich die einzelnen Schichten als technische Ausprägungen des OO-Modells. D.h. in jeder Schicht sollte sich das OO-Modell wiederfinden, aber nur die Teile des (abstrakten) Modells, die für die Schicht wirklich relevant sind. D.h. In der Persistenzschicht (z.B. EF) in erster Linie die zu speichernden Attribute, transiente Attribute (die es im abstrakten Modell ja auch geben kann) können hier vernachlässigt werden, sehr wohl aber natürlich irgendwelches Verhalten (Methoden), die auf die Datenbank lesend oder schreibend zugreifen muss. Für den Netzverkehr sind wohl wirklich nur die Attribute relevant, keinerlei Verhalten. Die View hingegen kann z.B. bestimmte clientseitige Prüfungen enthalten, die sich rein auf dem Client abspielen und keinerlei Aktion übers Netz geschweige denn die Datenbank haben.

      Was will ich damit sagen: Die pure Objektorientierung spiegelt sich wirklich nur als Modell wieder. Jede der angesprochenen Schichten beinhaltet nur eine Teilmenge des gedachten Objektmodells. Aber: Alle Schichten zusammen müssen sich konsistent verhalten, damit es funktioniert.

      Was fange ich nun mit diesem Ansatz an: Nun, ich designe mein (gedachtes) Modell in einem UML-Diagramm, und erzeuge daraus durch Codegenerierung den Code aller eben beschriebenen Schichten. Natürlich nicht Alles, aber einen nicht unbeträchtlichen Teil. OK, wir verlassen jetzt DDD und sind bei MDA, ich weiß 😉 Aber mit diesem Ansatz kann ich auch sehr komplexe Software (sofern sie denn gut objektorientiert designed ist) sehr effektiv entwickeln, was ich schon getan habe für ein System mit weit über 100 Klassen (etwa 250)

      Ich könnte noch viel drüber schreiben (sollte ich auch mal :-)) Ich muss aber jetzt los …
      Auf feedback von Euch freue ich mich

      Gruß Niko.

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