Rich-Domain-Modell mit ORM

8

Ich vermisse etwas und eine umfangreiche Nutzung von Google hat nicht geholfen, mein Verständnis zu verbessern ...

Hier ist mein Problem:
Ich möchte mein Domänenmodell in einer unwissenden Art und Weise erstellen, zum Beispiel:

  1. Ich möchte virtual nicht hinzufügen, wenn ich es sonst nicht brauche.
  2. Ich mag es nicht, einen Standardkonstruktor hinzuzufügen, weil ich mag, dass meine Objekte immer vollständig aufgebaut sind. Darüber hinaus ist die Notwendigkeit eines Standardkonstruktors im Kontext der Abhängigkeitsinjektion problematisch.
  3. Ich möchte keine allzu komplizierten Zuordnungen verwenden, da mein Domänenmodell Schnittstellen oder andere Konstrukte verwendet, die vom ORM nicht ohne weiteres unterstützt werden.

Eine Lösung wäre, getrennte Domänenobjekte und Datenentitäten zu haben. Das Abrufen der konstruierten Domänenobjekte könnte leicht unter Verwendung des Repository-Musters und des Aufbaus des Domänenobjekts aus der Dateneinheit, die von dem ORM zurückgegeben wird, gelöst werden. Mit AutoMapper wäre das trivial und nicht zu viel Code-Overhead.

Aber ich habe ein großes Problem mit diesem Ansatz: Es scheint, dass ich das Lazy Loading nicht wirklich unterstützen kann, ohne selbst Code dafür zu schreiben. Außerdem hätte ich ziemlich viele Klassen für das gleiche "Ding", besonders im erweiterten Kontext von WCF und UI:

  1. Datenentität (dem ORM zugeordnet)
  2. Domänenmodell
  3. WCF DTO
  4. Modell anzeigen

Meine Frage ist also: Was vermisse ich? Wie wird dieses Problem generell gelöst?

UPDATE:
Die bisherigen Antworten deuten auf das hin, was ich schon befürchtet habe: Es sieht so aus, als hätte ich zwei Möglichkeiten:

  1. Machen Sie Kompromisse im Domänenmodell, um die Voraussetzungen des ORM zu erfüllen, und haben Sie ein Domänenmodell, in das das ORM in
  2. mündet
  3. Erstellen Sie eine Menge zusätzlichen Code

UPDATE:
Zusätzlich zu der angenommenen Antwort finden Sie meine Antwort für konkrete Informationen darüber, wie ich diese Probleme für mich gelöst habe.

>     
Daniel Hilgarth 07.10.2011, 17:26
quelle

6 Antworten

4

Ich würde in Frage stellen, dass die Übereinstimmung mit den Voraussetzungen eines ORM notwendigerweise Kompromisse bedeutet. Einige davon sind jedoch vom Standpunkt einer stark SOLIDEN, lose gekoppelten Architektur aus gesehen.

Ein ORM-Framework existiert aus einem einzigen Grund; ein von Ihnen implementiertes Domänenmodell zu übernehmen und es in einer ähnlichen DB-Struktur zu erhalten, ohne dass Sie eine große Anzahl fehleranfälliger, nahezu unmöglicher SQL-Strings oder gespeicherter Prozeduren implementieren müssen. Sie implementieren auch leicht Konzepte wie Lazy-Loading; hydratisieren eines Objekts in der letzten Minute, bevor dieses Objekt benötigt wird, anstatt selbst ein großes Objektdiagramm zu erstellen.

Wenn Sie gespeicherte Prozeduren haben oder haben möchten und diese verwenden müssen (ob Sie wollen oder nicht), sind die meisten ORMs nicht das richtige Werkzeug für den Job. Wenn Sie eine sehr komplexe Domänenstruktur haben, sodass das ORM die Beziehung zwischen einem Feld und seiner Datenquelle nicht zuordnen kann, würde ich ernsthaft fragen, warum Sie diese Domäne und diese Datenquelle verwenden. Und wenn Sie 100% POCO-Objekte haben wollen, ohne dass Sie wissen, welcher Persistenzmechanismus dahinter steckt, werden Sie am Ende wahrscheinlich die meiste Macht eines ORMs nutzen, denn wenn die Domäne keine virtuellen Mitglieder oder untergeordnete Sammlungen hat das kann durch Proxies ersetzt werden, dann sind Sie gezwungen, den gesamten Objektgraphen eifrig zu laden (was möglicherweise unmöglich sein kann, wenn Sie eine massive verknüpfte Objektgrafik haben).

Während ORMs etwas Erfahrung in der Domäne des Persistenzmechanismus im Hinblick auf das Domänen-Design benötigen, führt ein ORM immer noch zu viel mehr SOLID-Designs, IMO. Ohne ein ORM sind dies Ihre Optionen:

  • Rollen Sie Ihr eigenes Repository, das eine Methode enthält, mit der Sie jede Art von "Top-Level" -Objekt in Ihrer Domain erzeugen und beibehalten können (ein "God Object" Anti-Pattern)
  • Erstellen Sie DAOs, die jeweils an einem anderen Objekttyp arbeiten. Für diese Typen müssen Sie das Get-Coding zwischen ADO DataReaders und Ihren Objekten fest codieren. im Durchschnitt vereinfacht eine Abbildung den Prozess erheblich. Die DAOs müssen auch voneinander wissen; Um eine Rechnung persistent zu machen, benötigen Sie das DAO für die Rechnung, das ein DAO für die InvoiceLine-, Customer- und GeneralLedger-Objekte benötigt. Und es muss einen gemeinsamen, abstrahierten Transaktionssteuerungsmechanismus geben, der in all dies integriert ist.
  • Richten Sie ein ActiveRecord-Muster ein, in dem Objekte persistent bleiben (und noch mehr Wissen über den Persistenzmechanismus in Ihre Domäne bringen)

Insgesamt ist die zweite Option die SOLIDE, aber meistens wird sie zu einem Biest - und zu zwei Dritteln zu pflegen, besonders wenn es sich um eine Domäne handelt, die Rückreferenzen und Zirkelbezüge enthält. Zum Beispiel kann sich ein InvoiceLineDetail-Datensatz (der vielleicht Lieferscheine oder Steuerinformationen enthält) für den schnellen Abruf und / oder Durchlauf direkt auf die Rechnung sowie auf die Rechnungszeile beziehen, zu der er gehört. Dies erzeugt eine kreisförmige 3-Knoten-Referenz, die entweder einen O (n ^ 2) -Algorithmus benötigt, um zu erkennen, dass das Objekt bereits behandelt wurde, oder eine hartcodierte Logik bezüglich eines "Kaskaden" -Verhaltens für die Rückwärtsreferenz. Ich musste vorher "Graph Walker" implementieren; Vertrauen Sie mir, Sie wollen dies nicht tun, wenn es andere Möglichkeiten gibt, diese Aufgabe zu erledigen.

Zusammenfassend ist meine Meinung, dass ORMs das geringste Übel sind, wenn man einen ausreichend komplexen Bereich betrachtet. Sie kapseln viel von dem, was nicht fest ist an Persistenzmechanismen, und reduzieren das Wissen über die Persistenz der Domäne auf sehr hohe Implementierungsdetails, die in einfache Regeln zerfallen ("alle Domänenobjekte müssen alle ihre öffentlichen Mitglieder als virtuell markiert haben").

    
KeithS 07.10.2011, 18:13
quelle
5

Kurz gesagt - es ist nicht gelöst

(hier kommen zusätzliche nutzlose Zeichen, um meine tolle Antwort zu posten)

    
Arnis Lapsa 07.10.2011 17:30
quelle
3

Alle guten Punkte.

Ich habe keine Antwort (aber der Kommentar ist zu lang geworden, als ich beschlossen habe, etwas über gespeicherte Prozeduren hinzuzufügen), außer zu sagen, dass meine Philosophie identisch zu deiner ist und ich Code oder Code erzeuge.

Dinge wie partielle Klassen machen dies viel einfacher als früher in den frühen .NET-Tagen. Aber ORMs (als ein eigenständiges "Ding" im Gegensatz zu etwas, das gerade in und von der Datenbank kommt) erfordern immer noch eine Menge Kompromisse und sie sind, offen gesagt, zu undeutlich für mich. Und ich habe nicht viele Dupe-Klassen, weil meine Designs ein sehr langes Leben haben und sich im Laufe der Jahre (sogar Jahrzehnte) stark verändern.

Soweit es die Datenbankseite betrifft, sind gespeicherte Prozeduren aus meiner Sicht eine Notwendigkeit. Ich weiß, dass ORMs sie unterstützen, aber die meisten ORM-Benutzer neigen dazu, dies nicht zu tun, und das ist für mich ein großes Negativ - weil sie über eine Best Practice sprechen und dann zu einem Tabellen-basierten Design passen, selbst wenn es erstellt wird von einem Code-First-Modell. Mir scheint, sie sollten sich einen Objektdatenspeicher ansehen, wenn sie eine relationale Datenbank nicht so verwenden wollen, dass sie ihre Stärken nutzt. Ich glaube zuerst an Code AND Database - d. H. Modelliere die Datenbank und das Objektmodell gleichzeitig vor und zurück und arbeite dann von beiden Seiten nach innen. Ich werde es hier verteilen:

Wenn Sie Ihren Entwicklern die Möglichkeit geben, ORM für Ihre Tabellen zu kodieren, wird Ihre App Probleme haben, jahrelang leben zu können. Tabellen müssen sich ändern. Immer mehr Leute werden gegen diese Entitäten anklopfen wollen, und jetzt benutzen sie alle ein ORM, das aus Tabellen generiert wird. Und Sie werden Ihre Tabellen im Laufe der Zeit umgestalten wollen. Darüber hinaus bieten nur Stored Procedures eine nutzbare, rollenbasierte Verwaltbarkeit, ohne dass jede Tabelle auf einer GRANT-Basis-Spalte behandelt werden muss - was äußerst schmerzhaft ist. Wenn Sie in OO gut programmieren, müssen Sie die Vorteile der kontrollierten Kopplung verstehen. Das sind alle gespeicherten Prozeduren - BENUTZEN SIE, damit Ihre Datenbank eine wohldefinierte Schnittstelle hat. Oder verwenden Sie keine relationale Datenbank, wenn Sie nur einen "dummen" Datenspeicher haben möchten.

    
Cade Roux 07.10.2011 17:43
quelle
0

Haben Sie sich den Entity Framework 4.1 Code zuerst angesehen? IIRC, die Domain-Objekte sind reine POCOs.

    
Jason Miesionczek 07.10.2011 17:30
quelle
0

das, was wir bei unserem letzten Projekt gemacht haben, und es hat ziemlich gut geklappt

  1. verwenden Sie EF 4.1 mit virtuellen Schlüsselwörtern für unsere Geschäftsobjekte und haben Sie unsere eigene Implementierung der T4-Vorlage. Wrapping des ObjectContext hinter einer Schnittstelle für den Datenaccess des Repository-Stils.
  2. Autoadapter verwenden, um zwischen Bo To DTO
  3. zu konvertieren
  4. Verwenden von autoMapper zum Konvertieren zwischen ViewModel und DTO.

Sie würden denken, dass Viewmodel- und Dto- und Business-Objekte die gleiche Sache sind, und sie könnten gleich aussehen, aber sie haben eine sehr klare Trennung in Bezug auf Bedenken. Ansichtsmodelle sind mehr über den UI-Bildschirm, DTO ist mehr über die Aufgabe, die Sie erledigen, und Geschäftsobjekte betreffen hauptsächlich die Domäne

Es gibt einige Kompromisse auf dem Weg, aber wenn Sie EF wollen, dann überwiegen die Vorteile die Dinge, die Sie aufgeben

    
np-hard 07.10.2011 17:43
quelle
0

Über ein Jahr später habe ich diese Probleme jetzt für mich gelöst.

Mit NHibernate kann ich recht komplexe Domänenmodelle auf vernünftige Datenbankdesigns abbilden, die einen DBA nicht erschüttern würden.

Manchmal ist es erforderlich, eine neue Implementierung der Schnittstelle IUserType zu erstellen, damit NHibernate einen benutzerdefinierten Typ korrekt beibehalten kann. Dank NHibernates erweiterbarer Natur, das ist keine große Sache.

Ich habe keine Möglichkeit gefunden, das Hinzufügen von virtual zu meinen Eigenschaften zu vermeiden, ohne das Lazy Loading zu verlieren. Ich mag es immer noch nicht besonders, besonders wegen all der Warnungen von Code Analysis über virtuelle Eigenschaften, ohne dass abgeleitete Klassen sie außer Kraft setzen, aber aus Pragmatismus kann ich jetzt damit leben.

Für den Standardkonstruktor habe ich auch eine Lösung gefunden, mit der ich leben kann. Ich füge die Konstruktoren hinzu, die ich als öffentliche Konstruktoren benötige, und füge einen veralteten geschützten Konstruktor für NHibernate hinzu:

%Vor%     
Daniel Hilgarth 17.01.2013 09:46
quelle

Tags und Links