it-swarm.dev

Sind DDD-Aggregate in einer Webanwendung wirklich eine gute Idee?

Ich beschäftige mich mit Domain Driven Design und einige der Konzepte, auf die ich stoße, sind an der Oberfläche sehr sinnvoll, aber wenn ich mehr darüber nachdenke, muss ich mich fragen, ob das wirklich eine gute Idee ist.

Das Konzept der Aggregate ist beispielsweise sinnvoll. Sie erstellen kleine Eigentumsdomänen, damit Sie sich nicht mit dem gesamten Domänenmodell befassen müssen.

Wenn ich jedoch im Zusammenhang mit einer Web-App darüber nachdenke, greifen wir häufig auf die Datenbank zu, um kleine Teilmengen von Daten abzurufen. Beispielsweise kann eine Seite nur die Anzahl der Bestellungen auflisten, mit Links zum Klicken, um die Bestellung zu öffnen und ihre Bestell-IDs anzuzeigen.

Wenn ich Aggregate richtig verstehe, würde ich normalerweise das Repository-Muster verwenden, um ein OrderAggregate zurückzugeben, das die Mitglieder GetAll, GetByID, Delete und Save. OK das hört sich gut an. Aber...

Wenn ich GetAll anrufe, um alle meine Bestellungen aufzulisten, scheint es mir, dass für dieses Muster die Rückgabe der gesamten Liste der aggregierten Informationen, vollständige Bestellungen, Bestellpositionen usw. erforderlich ist. Wenn ich nur eine kleine Teilmenge dieser Informationen benötige (nur Header-Informationen).

Vermisse ich etwas Oder gibt es eine Optimierungsstufe, die Sie hier verwenden würden? Ich kann mir nicht vorstellen, dass irgendjemand die Rückgabe ganzer Informationsaggregate befürworten würde, wenn Sie diese nicht benötigen.

Sicherlich könnte man Methoden in Ihrem Repository wie GetOrderHeaders erstellen, aber das scheint den Zweck der Verwendung eines Musters wie Repository in erster Linie zu vereiteln.

Kann mir das jemand erklären?

EDIT :

Nach viel mehr Recherche denke ich, dass die Trennung hier darin besteht, dass sich ein reines Repository-Muster von dem unterscheidet, was die meisten Leute von einem Repository halten.

Fowler definiert ein Repository als einen Datenspeicher, der die Sammlungssemantik verwendet und im Allgemeinen im Speicher gehalten wird. Dies bedeutet, dass ein gesamtes Objektdiagramm erstellt wird.

Evans ändert das Repository so, dass es aggregierte Roots enthält. Daher wird das Repository so amputiert, dass nur die Objekte in einem Aggregat unterstützt werden.

Die meisten Leute scheinen Repositorys als verherrlichte Datenzugriffsobjekte zu betrachten, bei denen Sie nur Methoden erstellen, um die gewünschten Daten abzurufen. Dies scheint nicht die Absicht zu sein, wie sie in Fowlers Patterns of Enterprise Application Architecture beschrieben ist.

Wieder andere betrachten ein Repository als eine einfache Abstraktion, die in erster Linie dazu dient, das Testen und Verspotten zu vereinfachen oder die Persistenz vom Rest des Systems zu entkoppeln.

Ich denke, die Antwort ist, dass dies ein viel komplexeres Konzept ist, als ich zuerst dachte.

43

Verwenden Sie Ihr Domain-Modell und Ihre Aggregate nicht zum Abfragen.

In der Tat ist das, was Sie stellen, eine häufig genug gestellte Frage, dass eine Reihe von Prinzipien und Mustern festgelegt wurde, um genau das zu vermeiden. Es heißt CQRS .

31
quentin-starin

Ich hatte und habe immer noch Probleme damit, das Repository-Muster in einem domänengesteuerten Design am besten zu verwenden. Nachdem ich es jetzt zum ersten Mal verwendet habe, habe ich mir die folgenden Methoden ausgedacht:

  1. Ein Repository sollte einfach sein. Es ist nur dafür verantwortlich, Domänenobjekte zu speichern und abzurufen. Alle andere Logik sollte sich in anderen Objekten wie Fabriken und Domänendiensten befinden.

  2. Ein Repository verhält sich wie eine Sammlung, als wäre es eine Sammlung von aggregierten Wurzeln im Speicher.

  3. Ein Repository ist kein generisches DAO. Jedes Repository verfügt über eine eindeutige und enge Schnittstelle. Ein Repository verfügt häufig über bestimmte Finder-Methoden, mit denen Sie die Sammlung nach der Domäne durchsuchen können (z. B.: Geben Sie mir alle offenen Bestellungen für Benutzer X). Das Repository selbst kann mit Hilfe eines generischen DAO implementiert werden.

  4. Im Idealfall geben die Finder-Methoden nur aggregierte Wurzeln zurück. Wenn dies zu ineffizient ist, kann es auch schreibgeschützte Wertobjekte zurückgeben, die genau das enthalten, was Sie benötigen (obwohl es ein Plus ist, wenn diese Wertobjekte auch in Bezug auf die Domäne ausgedrückt werden können). Als letztes Mittel kann das Repository auch verwendet werden, um Teilmengen oder Sammlungen von Teilmengen eines aggregierten Stamms zurückzugeben.

  5. Entscheidungen wie diese hängen von den verwendeten Technologien ab, da Sie einen Weg finden müssen, um Ihr Domänenmodell mit den verwendeten Technologien am effizientesten auszudrücken.

8
Kdeveloper

Ich glaube nicht, dass Ihre GetOrderHeaders-Methode den Zweck des Repositorys überhaupt zunichte macht.

DDD befasst sich (unter anderem) mit der Sicherstellung, dass Sie über das aggregierte Stammverzeichnis das erhalten, was Sie benötigen (Sie hätten beispielsweise kein OrderDetailsRepository), schränkt Sie jedoch nicht in der Art und Weise ein, wie Sie es erwähnen.

Wenn es sich bei einem OrderHeader um ein Domänenkonzept handelt, sollten Sie es als solches definieren und über die entsprechenden Repository-Methoden zum Abrufen verfügen. Stellen Sie einfach sicher, dass Sie dabei die richtige aggregierte Wurzel durchlaufen.

6
Eric King

Meine Verwendung von DDD wird möglicherweise nicht als "reine" DDD betrachtet, aber ich habe die folgenden realen Strategien unter Verwendung von DDD für einen DB-Datenspeicher angepasst.

  • Einem aggregierten Stamm ist ein Repository zugeordnet
  • Das zugehörige Repository wird nur von diesem aggregierten Stamm verwendet (es ist nicht öffentlich verfügbar).
  • Ein Repository kann Abfrageaufrufe enthalten (z. B. GetAllActiveOrders, GetOrderItemsForOrder).
  • Ein Dienst macht eine öffentliche Teilmenge des Repositorys und andere nicht grobe Vorgänge verfügbar (z. B. Geld von einem Bankkonto auf ein anderes überweisen, LoadById, Suchen/Suchen, CreateEntity usw.).
  • Ich benutze den Root -> Service -> Repository Stack. Es wird nur angenommen, dass ein DDD-Dienst für alles verwendet wird, was eine Entität nicht selbst beantworten kann (z. B. LoadById, TransferMoneyFromAccountToAccount), aber in der realen Welt bleibe ich in der Regel auch bei anderen CRUD-bezogenen Diensten (Speichern, Löschen, Abfragen), obwohl die root sollte in der Lage sein, diese selbst zu "beantworten/auszuführen". Beachten Sie, dass es nichts Falsches ist, einer Entität Zugriff auf einen anderen aggregierten Stammdienst zu gewähren! Denken Sie jedoch daran, dass Sie nicht in einen Dienst (GetOrderItemsForOrder), sondern in das Repository aufgenommen werden, damit das Aggregatstammverzeichnis davon Gebrauch machen kann. Beachten Sie, dass ein Dienst keine offenen Abfragen wie das Repository verfügbar machen sollte.
  • Normalerweise definiere ich ein Repository abstrakt im Domänenmodell (über die Schnittstelle) und stelle eine separate konkrete Implementierung bereit. Ich definiere einen Dienst vollständig im Domänenmodell, das in ein konkretes Repository zur Verwendung eingefügt wird.

** Sie müssen nicht ein ganzes Aggregat zurückbringen. Wenn Sie jedoch mehr möchten, müssen Sie den Stamm fragen, nicht einen anderen Dienst oder ein anderes Repository. Dies ist ein verzögertes Laden und kann entweder manuell mit einem faulen Laden eines armen Mannes (Einfügen des entsprechenden Repositorys/Dienstes in das Stammverzeichnis) oder mithilfe von ORM erfolgen, das dies unterstützt.

In Ihrem Beispiel würde ich wahrscheinlich einen Repository-Aufruf bereitstellen, der nur die Auftragskopfzeilen enthält, wenn ich die Details in einen separaten Aufruf laden möchte. Beachten Sie, dass wir mit einem "OrderHeader" tatsächlich ein zusätzliches Konzept in die Domain einführen.

4
Mike Rowley

Ihr Domain-Modell enthält Ihre Geschäftslogik in ihrer reinsten Form. Alle Beziehungen und Vorgänge, die den Geschäftsbetrieb unterstützen. Was Sie in Ihrer konzeptionellen Zuordnung vermissen, ist die Idee des Application Service Layer , den der Service Layer um das Domänenmodell herum umschließt und eine vereinfachte Ansicht der Geschäftsdomäne bietet (eine Projektion, wenn Sie so wollen), die dies ermöglicht Das Domänenmodell muss nach Bedarf geändert werden, ohne dass sich dies direkt auf die Anwendungen auswirkt, die die Serviceschicht verwenden.

Weitergehen. Die Idee des Aggregats ist, dass es ein Objekt gibt, die Aggregatwurzel, die für die Aufrechterhaltung der Konsistenz des Aggregats verantwortlich ist. In Ihrem Beispiel ist die Bestellung für die Manipulation ihrer Bestellpositionen verantwortlich.

In Ihrem Beispiel würde die Service-Schicht einen Vorgang wie GetOrdersForCustomer verfügbar machen, der nur das zurückgibt, was zum Anzeigen einer zusammenfassenden Liste der Bestellungen erforderlich ist (wie Sie sie OrderHeaders nennen).

Schließlich ist das Repository-Muster nicht NUR eine Sammlung, sondern ermöglicht auch deklarative Abfragen. In C # können Sie LINQ als Abfrageobjekt verwenden, oder die meisten anderen O/RMs bieten auch eine Abfrageobjektspezifikation.

Ein Repository vermittelt zwischen der Domänen- und der Datenzuordnungsschicht und verhält sich wie eine speicherinterne Domänenobjektsammlung. Clientobjekte erstellen Abfragespezifikationen deklarativ und senden sie zur Zufriedenheit an das Repository. (von Fowler's Repository Seite )

Da Sie Abfragen für das Repository erstellen können, ist es auch sinnvoll, praktische Methoden bereitzustellen, die allgemeine Abfragen verarbeiten. Das heißt, Wenn Sie nur die Überschriften Ihrer Bestellung möchten, können Sie eine Abfrage erstellen, die nur die Überschriften zurückgibt, und diese über eine praktische Methode in Ihren Repositorys verfügbar machen.

Hoffe, das hilft, die Dinge zu klären.

3
Michael Brown

Ich weiß, dass dies eine alte Frage ist, aber ich scheine zu einer anderen Antwort gekommen zu sein.

Wenn ich ein Repository erstelle, werden im Allgemeinen einige zwischengespeicherte Abfragen eingeschlossen.

Fowler definiert ein Repository als einen Datenspeicher, der die Sammlungssemantik verwendet und im Allgemeinen im Speicher gehalten wird. Dies bedeutet, dass ein gesamtes Objektdiagramm erstellt wird.

Behalten Sie diese Repositorys in Ihrem Server-RAM. Sie passieren nicht nur Objekte an die Datenbank!

Wenn ich mich in einer Webanwendung mit einer Seite befinde, auf der Bestellungen aufgelistet sind, auf die Sie klicken können, um Details anzuzeigen, möchte ich wahrscheinlich, dass meine Seite mit Bestelllisten Details zu den Bestellungen enthält (ID, Name, Betrag, Datum). um einem Benutzer bei der Entscheidung zu helfen, welche er sich ansehen möchte.

Zu diesem Zeitpunkt haben Sie zwei Möglichkeiten.

  1. Sie können die Datenbank abfragen und genau das zurückziehen, was Sie für die Auflistung benötigen. Anschließend können Sie erneut abfragen, um die einzelnen Details abzurufen, die auf der Detailseite angezeigt werden sollen.

  2. Sie können eine Abfrage durchführen, die alle Informationen zurückzieht und zwischenspeichert. Auf der nächsten Seite lesen Sie die Anforderung vom Server-RAM anstelle der Datenbank. Wenn der Benutzer zurückschlägt oder die nächste Seite auswählt, werden immer noch keine Fahrten zur Datenbank durchgeführt.

In Wirklichkeit ist es genau das, wie Sie es implementieren, und Implementierungsdetails. Wenn mein größter Benutzer 10 Bestellungen hat, möchte ich wahrscheinlich Option 2 wählen. Wenn ich 10.000 Bestellungen spreche, wird Option 1 benötigt. In beiden oben genannten Fällen und in vielen anderen Fällen möchte ich, dass das Repository diese Implementierungsdetails verbirgt.

Wenn ich ein Ticket erhalte, um dem Benutzer mitzuteilen, wie viel er im letzten Monat für Bestellungen ( aggregierte Daten ) auf der Seite mit der Bestellliste ausgegeben hat, Würde ich lieber die Logik schreiben, um dies in SQL zu berechnen und einen weiteren Roundtrip zur Datenbank durchzuführen, oder würde ich sie lieber anhand der Daten berechnen, die sich bereits im Server-RAM befinden?

Nach meiner Erfahrung bieten Domain-Aggregate enorme Vorteile.

  • Sie sind eine große Wiederverwendung von Teilecodes, die tatsächlich funktioniert.
  • Sie vereinfachen den Code, indem sie Ihre Geschäftslogik direkt in der Kernschicht belassen, anstatt eine Infrastrukturschicht durchlaufen zu müssen, damit der SQL Server dies tut.
  • Sie können auch Ihre Antwortzeiten erheblich verkürzen, indem sie die Anzahl der erforderlichen Abfragen reduzieren, da Sie sie problemlos zwischenspeichern können.
  • Das SQL, das ich schreibe, ist oft viel wartbarer, da ich oft nur nach allem frage und die Serverseite berechne.
0
WhiteleyJ