Freigeben über



Juli 2016

Band 31, Nummer 7

Innovation – Überlegungen zu Code First, Persistenz und Domänenmodellierung

Von Dino Esposito | Juli 2016

Dino EspositoCode First ist ein Funktionselement von Entity Framework (EF), mit dem Sie Datenbanktabellen mithilfe einfacher .NET-Klassen modellieren können. Ehrlich gesagt, finde ich den Namen Code First ein wenig irreführend, doch die Aufgaben, die sich damit erledigen lassen, sind ganz eindeutig. Code First ordnet die Struktur der verwendeten Datenbank an und bietet eine allgemeine objektorientierte API zum Arbeiten mit gespeicherten Daten.

Code First wurde in EF4.1 erstmals eingeführt und ist bis EF6 durchgängig enthalten – nur einer der Ansätze, den Sie zum Modellieren Ihrer Datenbank mit C#- oder Visual Basic-Klassen verfolgen können. Bis EF6 können Sie auch einen Visual Studio-Designer zum Ableiten des Schemas der Datenbank nutzen, es in einer XML-Datei mit der Erweiterung EDMX speichern und Ad-hoc-Klassen zur Verwendung in Code erstellen. Der Visual Studio-Designer ermöglicht Ihnen auch das Erstellen eines abstrakten Modells, mit dessen Hilfe später eine physische Datenbank erstellt wird.

Kurz gesagt, gab es bis EF6 zwei Möglichkeiten zum Erledigen derselben Aufgabe, doch der EDMX-Ansatz ist, wenngleich funktionsfähig, problematischer als der andere. Aus diesem Grund wurde die Unterstützung von EDMX aus dem anstehenden EF7 entfernt.

Über die Jahre war Code First stets mit Domain-Driven Design (DDD) verbunden. Dies mag zu der allgemeinen Vorstellung beigetragen haben, dass Code First und EDMX nicht exakt zwei Möglichkeiten zum Erledigen derselben Aufgabe sind. In dieser Kolumne biete ich einen eher architekturbezogenen Blick auf Code First und unterscheide den Bereich des Domänenmodells vom Bereich des Persistenzmodells. Code First lässt zusammen mit LINQ einen alten Traum der meisten Entwickler wahr werden: Es blendet die Feinheiten des Datenzugriffs (Tabellen, Indizes, Einschränkungen) hinter einer objektorientierten Fassade aus und geht als die objektorientierte Datendefinitionssprache durch, die Sie nie hatten.

Historischer Hintergrund

Beim Arbeiten mit relationalen Datenbanken folgen Sie den Regeln der Sprache SQL. Bei der Programmierung von Anwendungen befolgen Sie stattdessen die Regeln der Programmiersprache Ihrer Wahl. Deshalb ist eine Abstraktionsebene erforderlich, um eine Brücke zwischen der objektorientierten bzw. prozeduralen Natur von Programmiersprachen oberster Ebene und der Sprache SQL zu schlagen. In Microsoft .NET Framework ist diese Abstraktionsschicht das ADO.NET-Framework.

ADO.NET ist eine relativ schlanke Abstraktionsschicht in dem Sinne, dass nur Objekte für Ihren .NET-Code zum Platzieren von SQL-Befehlen bereitgestellt werden. ADO.NET ordnet an die Datenbank gesendete oder von dieser empfangene Daten nicht objektorientierten Ad-hoc-Datenstrukturen zu. In ADO.NET sind die Tools zum Abrufen der Daten vollständig mit dem umgebenden .NET Framework zusammengeführt, doch die Daten sind flach.

Vor ca. 10 Jahren kamen O/RM-Frameworks (Object/Relational Mapper) auf den Markt. Ein O/RM-Framework ordnet die Eigenschaften einer Klasse den Spalten einer Tabelle zu. Dabei implementiert es verschiedene Entwurfsmuster wie Data Mapper, Unit of Work und Query Object. Ein O/RM-Framework verwaltet auch intern verschiedene Zuordnungsregeln und Informationen zum Schema der Zieldatenbank. Dies sind konkrete Informationen, die irgendwo und irgendwie gespeichert werden müssen. NHibernate, das erste O/RM-Framework im .NET-Bereich, speichert diese Informationen in einer XML-Datei. Für EF wurde anfänglich mit EDMX-Dateien derselbe Ansatz gewählt und ein praktischer Designer für ihre Verwaltung in Visual Studio hinzugefügt. Code First ordnet Spalten und Tabellen Klasseneigenschaften über entweder Attribute oder eine Fluent-API (mit mehr Möglichkeiten) zu.

In einem vor mehreren Monaten veröffentlichtem Blogbeitrag erläuterte das EF-Team auf klare Weise die Motivation, Code First zur einzigen unterstützten Möglichkeit zum Speichern von Datenmodellen in EF7 zu machen. (Den vollständigen Beitrag finden Sie unter bit.ly/1sLM3Ur.) In dem Beitrag wird der Ausdruck „codebasierte Modellierung“ als besser erklärender Name für das verwendet, was Code First eigentlich macht. Ich bin damit mehr als einverstanden.

Kurzübersicht über DDD

DDD ist eine Ansatz für die Softwareentwicklung, der ursprünglich als ein Satz von Regeln erdacht wurde, die systematisch angewendet werden, um einen monumentalen Grad von Komplexität (d. h. sehr viele Geschäftsregeln und Entitäten) in den Griff zu bekommen. Während die Qualitäten von DDD in sehr großen Systemen mit mindestens Hunderten von Regeln und Entitäten besonders zum Tragen kommen, hat es auch in einfacheren Szenarien einen großen Nutzen für Entwickler und Systemarchitekten. Im Grund gibt es keine Argumente, die gegen das Umsetzen bestimmter Teile von DDD in nahezu jedem Softwareprojekt sprechen. Der Teil von DDD, der in jedem Projekt nützlich ist, nennt sich „Strategisches Design“ und basiert auf dem Befolgen einiger bekannter Methoden: Ubiquitäre Sprache, Kontextgrenze und Kontextzuordnung. Diese Analysemuster haben wenig zu tun mit den tatsächlichen Klassen und Datenbanktabellen, die Sie in der endgültigen Anwendung nutzen, auch wenn das letztliche Ziel ihrer Verwendung das effektivere Schreiben von Code ist. Die strategischen Entwurfsmuster von DDD zielen auf die Analyse der Geschäftsdomäne und das Vergegenwärtigen der Architektur oberster Ebene des resultierenden Systems ab. Abbildung 1 zeigt eine mögliche Architektur oberster Ebene einer E-Commerce-Lösung. Jeder Block stellt eine Kontextgrenze dar, die während der Analyse bestimmt und zum Beschleunigen der Entwicklung eingeführt wurde.

Beispiel einer Architektur oberster Ebene mit Kontextgrenzen
Abbildung 1: Beispiel einer Architektur oberster Ebene mit Kontextgrenzen

Jede Kontextgrenze, die Sie bei Ihrer Analyse bestimmen, hat eine eigene Geschäftssprache, eigene Architektur (einschließlich Technologien) und eigene Menge von Beziehungen mit anderen Kontextgrenzen. Jede Kontextgrenze kann anschließend mithilfe der Softwarearchitektur implementiert werden, die einer bestimmten Anzahl und den Kompetenzen der beteiligten Teams, Einschränkungen hinsichtlich Budget und Zeit sowie den Belangen anderer Beteiligter entspricht, z. B. in Bezug auf vorhandene Softwarelizenzen, Kosten, Kenntnisse, Richtlinien usw. DDD macht auch einen klaren Vorschlag zur möglicherweise effektivsten Möglichkeit der Entwicklung für eine Kontextgrenze: die Schichtenarchitektur.

Das Domänenmodell in einer Schichtenarchitektur

Abbildung 2 zeigt das Wesentliche einer Schichtenarchitektur. Sie hat vier Schichten: Präsentation, Infrastruktur, Anwendung und Domäne. Es handelt sich also um eine verallgemeinerte Form der bekannten Drei-Schichten-Architektur (Präsentation, Geschäft, Daten) mit einer übersichtlichen Trennung zwischen Anwendungsfalllogik, die sich mit den Anwendungsfällen ändert, die Sie auf der Präsentationsschicht berücksichtigen, und Domänenlogik, die inhärent für das jeweilige Geschäftsgebaren ist und für alle Anwendungsfälle und Präsentationsschichten gilt.

Schema einer Schichtenarchitektur
Abbildung 2: Schema einer Schichtenarchitektur

Die Infrastrukturschicht umfasst alles, was für das Implementieren und Unterstützen von Anwendungsfällen und die Persistenz des Status der Domänenentitäten erforderlich ist. Die Infrastrukturschicht enthält demnach Komponenten, die die Verbindungszeichenfolge zum Herstellen einer Verbindung mit der Datenbank kennen.

Zentral für den DDD-Ansatz ist die Vorstellung eines „Domänenmodells“. Ein Domänenmodell ist einfach ein Softwaremodell, das Sie zum vollständigen Darstellen der Geschäftsdomäne erstellen. Anders ausgedrückt, dreht es sich um alles, was Sie mit Software tun können, um mit der gewünschten Domäne umzugehen. Ein Domänenmodell wird meist mit Entitäten, Ereignissen und Wertobjekten aufgefüllt. Dabei arbeiten einige der Entitäten und Wertobjekte zusammen, um eine unauflösbare Einheit zu bilden. Beim DDD wird dies als „Aggregat“ bezeichnet, und der Stamm des Aggregats ist der „Aggregatsstamm“. Persistenz erfolgt auf der Ebene der Aggregatsstämme, und der Aggregatsstamm ist in der Regel zuständig für die Persistenz aller anderer Entitäten und Wertobjekte im Aggregat.

Wie wird ein Aggregat von Entitäten und Werttypen programmiert? Das hängt vom verwendeten Programmierungsparadigma ab. Ein Domänenmodell ist zumeist ein objektorientiertes Modell, bei dem Entitäten Klassen mit Eigenschaften und Methoden und Wertobjekte unveränderliche Datenstrukturen sind. Das Verwenden einer funktionalen Sprache und unveränderlicher Datenstrukturen ist jedoch eine Option, zumindest bei bestimmten Typen von Geschäftsdomänen.

Code First ist eine konkrete Technologie mit strengem Bezug zur Leistung von Datenzugriffsaufgaben. Der charakteristischste Aspekt von Code First ist die Verwendung von Klassen zum Darstellen des zugrunde liegenden Schemas von Tabellen und der von der Anwendung verwendeten Daten. Entsprechen die von der Anwendung verwendeten Daten den Daten, die von der Anwendung mithilfe relationaler Tabellen persistent gespeichert werden? Oder ist anders gefragt die Gruppe der Klassen, die Code First zum Zuordnen von Tabellen in der relationalen Datenbank nutzt, identisch mit dem Domänenmodell der Anwendung? Ich würde sagen, meistens nein, doch was Softwarearchitektur betrifft, kommt es wie immer darauf an.

Das Domänenmodell in einer Schichtenarchitektur

Code First wird mitunter mit DDD aufgrund seiner Fähigkeit in Verbindung gebracht, die Daten einer Anwendung mittels Klassen zu modellieren. Während es fallweise durchaus akzeptabel ist, über einen einzelnen Satz von Klassen zu verfügen, die für sowohl die Geschäftslogik der Domäne als auch Persistenzaspekte zuständig sind, werden das Domänenmodell und das Persistenzmodell im Allgemein getrennt. Das Domänenmodell ist das Softwaremodell, mit dem Sie die Domänenlogik des Systems ausdrücken und seine Geschäftsregeln implementieren. Es kann sich um ein objektorientiertes Modell sowie um ein funktionales Modell oder gar um eine einfache Auflistung statischer Methoden handeln, die mittels Hilfsklassen verfügbar gemacht werden.

Der Punkt bei DDD ist, dass Sie Persistenzaspekte vom Domänenmodell fernhalten und sich beim Entwurf des Domänenmodells mehr darauf konzentrieren, was eine Geschäftsentität tut (und wie sie verwendet wird), anstatt auf die Daten, die sie enthält und verwaltet. Mithilfe eines verhaltensorientierten Ansatzes wird ein monumentaler Grad an Komplexität auf einen Grad heruntergebrochen, der mithilfe von Code effektiv bewältigt werden kann. Lassen Sie uns nun einfaches Beispiel, ein Spiel zweier Sportmannschaften wie in Abbildung 3, betrachten.

Verhalten im Vergleich zu Daten in der Entität eines Domänenmodells
Abbildung 3: Verhalten im Vergleich zu Daten in der Entität eines Domänenmodells

Zum Ausdrücken des Verhaltens einer Spielentität im Kontext eines Punktesystems modellieren Sie Aktionen wie „Start“, „Finish“, „Goal“, „Timeout“ und was sonst in dem spezifischen Szenario sinnvoll ist. Diese Methoden implementieren sämtliche Geschäftsregeln und stellen sicher, dass nur Aktionen im Einklang mit dem aktuellen Status der Entität programmgesteuert erfolgen. Die Methode „Goal“ wird beispielsweise ausgelöst, falls sie für eine „Match“-Instanz aufgerufen wird, die derzeit aufgrund eines Timeouts angehalten ist. Der interne Status der „Match“-Entität enthält alle diese Eigenschaften, die Sie üblicherweise mit solch einer Entität in einem rein relationalen Modell assoziieren, außer dass diese Eigenschaften schreibgeschützt sind und nur intern mittels Methoden aktualisiert werden.

Nicht alle Klassen, die ggf. in einem Domänenmodell vorhanden sind, müssen persistent gespeichert werden, und Persistenz kann für alle Eigenschaften oder nur einige wenige gelten. Bei Code First geht es also nicht um die Domänenmodellierung im Allgemeinen, sondern seine API, die Eigenschaften zu Tabellenspalten zuordnet, kann zum persistenten Speichern der Klassen in Ihrem Domänenmodell verwendet werden, die persistent gespeichert werden müssen. Auf diese Weise verfügen Sie über ein einzelnes Modell für die Domäne, das sowohl Geschäfts- als auch Persistenzanforderungen erfüllt.

Das Problem privater Setter

Aus Sicht der Domänenmodellierung arbeiten Sie nur mit den Entitäten folgenden Geschäftsworkflows, die von Domänenexperten skizziert werden. Wenn wir uns nochmals das Beispiel mit dem Spiel und den Punkten ansehen, ist es ggf. nicht in Einklang mit Geschäftsregeln, die den Status des Spiels oder Punktestands programmgesteuert festlegen. Status und Punktestand ändern sich nämlich mit Fortschreiten des Workflows. Gleichfalls verfügen Sie nicht über einen standardmäßigen parameterlosen Konstruktor, da dieser eine „Match“-Entität ohne verschiedene wichtige Informationen zurückgeben würde, wie z. B. Namen der teilnehmenden Mannschaften und eine ID, mit der das Spiel sinnvoll an einen Wettbewerb gebunden würde. Wenn Sie dennoch ein einzelnes Modell für Geschäft und Persistenz nutzen, ist ein parameterloser Konstruktor erforderlich, denn andernfalls kann EF nach einer Abfrage keine Instanz des Typs zurückgeben.

Doch es gibt noch mehr zu berücksichtigen. Wenn EF eine Abfrage ausführt und eine Instanz der „Match“-Klasse zurückgibt, muss es auf die Setter aller Eigenschaften zugreifen, um die zurückgegebene Instanz mit einem Status zu speichern, der in Einklang mit den Informationen in der Datenbank ist. Diese plausible Anforderung von EF steht in Konflikt mit den Entwurfsregeln eines Domänenmodells. Allgemeiner muss eine Möglichkeit zum Erzwingen eines Status für eine Entität eines Domänenmodells vorhanden sein, und zumeist muss diese intern und nicht über Code außerhalb der Schicht öffentlich zugänglich sein. Dies ist einer der Zwecke von Domänendiensten, die zusammen mit der Domäne die Domänenschicht in Abbildung 2 bilden. Bei Verwenden von Code First können Sie dasselbe erreichen, indem Sie einfach Setter als nicht öffentlich (intern, geschützt oder sogar privat) markieren und einen Standardkonstruktor mit derselben nicht öffentlichen Sichtbarkeit hinzufügen. EF findet weiter eine Möglichkeit des Zugriffs auf private Member zum Erzwingen eines Status (über Reflektion), öffentliche Clients der Domänen-API dagegen nicht. Nun erst, wenn sie selbst die Reflektion nutzen.

Zusammenfassung

Beim Surfen durch das Internet ist es nicht ungewöhnlich, Artikel zu finden, in denen Code First in Beziehung zu DDD gesetzt wird. Bei Code First geht es um Persistenz eines objektorientierten Modells, das einer Gruppe von Tabellen explizit zugeordnet ist. Aus konzeptueller Sicht ist das Domänenmodell etwas völlig anderes, das sich außerdem auf einer anderen Schicht befindet. Doch aufgrund einiger spezifischer Funktionen der Code First-API in Bezug auf den Umgang mit privaten Settern und Konstruktoren ist es mitunter möglich, ein einzelnes objektorientiertes Modell einzusetzen, das Verhaltens- und Geschäftsregeln einschließt und mühelos persistent in einer relationalen Datenbank gespeichert werden kann.


Dino Espositoist Autor von „Microsoft .NET: Architecting Applications for the Enterprise“ (Microsoft Press, 2014) und „Modern Web Applications with ASP.NET“ (Microsoft Press, 2016). Esposito ist Technical Evangelist für die .NET- und Android-Plattformen bei JetBrains und spricht häufig auf Branchenveranstaltungen weltweit. Auf software2cents.wordpress.com und auf Twitter unter twitter.com/despos lässt er uns wissen, welche Softwarevision er verfolgt.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Jon Arne Saeteras