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

40

Ich beschäftige mich mit Domain Driven Design und einige der Konzepte, denen ich begegne, sind auf den ersten Blick sehr sinnvoll, aber wenn ich mehr über sie nachdenke, muss ich mich fragen, ob das wirklich eine gute Idee ist.

Das Konzept der Aggregate macht zum Beispiel Sinn. Sie erstellen kleine Eigentumsdomänen, damit Sie sich nicht mit dem gesamten Domänenmodell befassen müssen.

Wenn ich im Zusammenhang mit einer Web-App darüber nachdenke, stoßen wir häufig auf die Datenbank, um kleine Teilmengen von Daten zurückzugewinnen. Zum Beispiel kann eine Seite nur die Anzahl der Bestellungen mit Links auflisten, auf die Sie klicken können, um die Bestellung zu öffnen und ihre Bestell-IDs anzuzeigen.

Wenn ich recht Verständnis Aggregates bin, würde ich in der Regel die Repository - Muster verwenden , um eine OrderAggregate zurück, die die Mitglieder enthalten würde GetAll, GetByID, Delete, und Save. OK das hört sich gut an. Aber...

Wenn ich GetAll aufrufe, um alle meine Bestellungen aufzulisten, scheint es mir, dass für dieses Muster die gesamte Liste der gesammelten Informationen zurückgegeben werden muss, Bestellungen, Bestellpositionen usw. abgeschlossen werden müssen. 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 sich irgendjemand dafür aussprechen würde, ganze Informationsaggregate zurückzugeben, wenn Sie sie nicht benötigen.

Sicherlich könnte man Methoden auf Ihrem Repository wie erstellen GetOrderHeaders, aber das scheint den Zweck der Verwendung eines Musters wie Repository an erster Stelle zu zunichte zu machen.

Kann das jemand für mich klären?

BEARBEITEN:

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 Auflistungssemantik verwendet und im Allgemeinen im Arbeitsspeicher aufbewahrt wird. Dies bedeutet, dass ein komplettes Objektdiagramm erstellt wird.

Evans ändert das Repository so, dass es Aggregate Roots enthält, und daher wird das Repository amputiert, um nur die Objekte in einem Aggregat zu unterstützen.

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, die in Fowlers Patterns of Enterprise Application Architecture beschrieben wird.

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.

Erik Funkenbusch
quelle
4
"Ich denke, die Antwort ist, dass dies ein viel komplexeres Konzept ist, als ich zuerst dachte." Das ist sehr richtig.
Quentin-Starin
In Ihrer Situation können Sie einen Proxy für das aggregierte Stammobjekt erstellen, das Daten nur dann selektiv abruft und zwischenspeichert, wenn dies angefordert wird
Steven A. Lowe,
Mein Vorschlag ist, Lazy Load in den Root-Aggregat-Assoziationen zu implementieren. So können Sie eine Liste der Wurzeln abrufen, ohne zu viele Objekte zu laden.
Juliano
4
Fast 6 Jahre später noch eine gute Frage. Nach dem Lesen des Kapitels im Roten Buch würde ich sagen: Machen Sie Ihre Aggregate nicht zu groß. Es ist verlockend, ein Konzept der obersten Ebene aus Ihrer Domain auszuwählen und es als Root zu deklarieren, um über alle zu entscheiden, aber DDD befürwortet kleinere Aggregate. Und mindert Ineffizienzen wie die oben beschriebenen.
Cpt. Senkfuss
Ihre Aggregate sollten so klein wie möglich sein und gleichzeitig natürlich und effektiv für die Domäne (eine Herausforderung!). Darüber hinaus ist es völlig in Ordnung und wünschenswert für Ihre repos hoch haben spezifische Methoden.
Timo

Antworten:

30

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

Was Sie fragen, ist in der Tat eine Frage, die weit genug verbreitet ist, dass eine Reihe von Grundsätzen und Mustern festgelegt wurde, um genau das zu vermeiden. Es heißt CQRS .

Quentin-Starin
quelle
2
@Mystere Man: Nein, es dient dazu , die minimal benötigten Daten bereitzustellen . Dies ist einer der Hauptzwecke eines getrennten Lesemodells. Es hilft auch dabei, einige Nebenläufigkeitsprobleme im Vorfeld anzugehen. CQRS hat mehrere Vorteile, wenn es auf DDD angewendet wird.
Quentin-Starin
2
@Mystere: Ich bin ziemlich überrascht, dass du das vermissen würdest, wenn du den Artikel liest, auf den ich verlinkt habe. Weitere Informationen finden Sie im Abschnitt "Abfragen (Berichterstellung)": "Wenn eine Anwendung Daten anfordert ... sollte dies in einem einzigen Aufruf der Abfrageschicht erfolgen. Im Gegenzug erhält sie ein einziges DTO, das alle erforderlichen Daten enthält. Der Grund dafür ist, dass Daten normalerweise um ein Vielfaches abgefragt werden, als das Domain-Verhalten ausgeführt wird. Wenn Sie dies optimieren, verbessern Sie die wahrgenommene Leistung des Systems. "
Quentin-Starin
4
Aus diesem Grund würden wir kein Repository zum Lesen von Daten in einem CQRS-System verwenden. Wir würden eine einfache Klasse schreiben, um eine Abfrage zu kapseln (unter Verwendung der jeweils geeigneten oder erforderlichen Technologie, oft passt ADO.Net oder Linq2Sql oder SubSonic gut hierher) und nur (alle) Daten zurückgeben, die für die jeweilige Aufgabe benötigt werden, um dies zu vermeiden Ziehen von Daten durch alle normalen Ebenen eines DDD-Repository. Wir würden ein Repository nur zum Abrufen eines Aggregats verwenden, wenn wir einen Befehl an die Domäne senden möchten.
Quentin-Starin
9
"Ich kann mir nicht vorstellen, dass sich irgendjemand dafür aussprechen würde, ganze Datenmengen zurückzugeben, wenn Sie sie nicht benötigen." Ich versuche zu sagen, dass Sie mit dieser Aussage genau richtig sind. Rufen Sie nicht eine ganze Sammlung von Informationen ab, wenn Sie sie nicht benötigen. Dies ist der Kern von CQRS für DDD. Zum Abfragen benötigen Sie kein Aggregat. Rufen Sie die Daten über einen anderen Mechanismus ab und führen Sie dies dann konsequent durch.
Quentin-Starin
2
@qes In der Tat ist es die beste Lösung, DDD nicht für Abfragen (Lesen) zu verwenden :) Sie verwenden DDD jedoch weiterhin im Befehlsteil, dh zum Speichern oder Aktualisieren von Daten. Ich habe eine Frage an Sie: Verwenden Sie immer Repositorys mit Entitäten, wenn Sie Daten in der Datenbank aktualisieren müssen? Nehmen wir an, Sie müssen nur einen kleinen Wert in der Spalte ändern (eine Art Schalter), laden Sie immer noch die gesamte Entität in die App-Ebene, ändern einen Wert (eine Eigenschaft) und speichern dann die gesamte Entität zurück in die DB? Ein bisschen Overkill auch?
Andrew
8

Ich habe Probleme mit der optimalen Verwendung des Repository-Musters in einem domänengetriebenen Design und habe immer noch Probleme. Nachdem ich es jetzt zum ersten Mal benutzt habe, habe ich die folgenden Methoden entwickelt:

  1. Ein Repository sollte einfach sein. Es ist nur für das Speichern und Abrufen von Domänenobjekten verantwortlich. Alle andere Logik sollte sich in anderen Objekten wie Fabriken und Domänendiensten befinden.

  2. Ein Repository verhält sich wie eine Sammlung, als ob es sich um eine Sammlung aggregierter Stammdaten im Arbeitsspeicher handelt.

  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 anhand der Domäne durchsuchen können (z. B. 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 letzter Ausweg kann das Repository auch verwendet werden, um Teilmengen oder Sammlungen von Teilmengen einer aggregierten Wurzel zurückzugeben.

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

Kentwickler
quelle
Es ist definitiv ein komplexes Thema. Es ist schwierig, Theorie in Praxis umzusetzen, besonders wenn man zwei unterschiedliche und getrennte Theorien in einer einzigen Praxis kombiniert.
Sebastian Patten
6

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

DDD ist (unter anderem) darum bemüht, sicherzustellen, dass Sie das, was Sie benötigen, über das aggregierte Stammverzeichnis erhalten (Sie hätten zum Beispiel kein OrderDetailsRepository), aber es schränkt Sie nicht in der Art und Weise ein, wie Sie es erwähnen.

Wenn ein OrderHeader ein Domain-Konzept ist, 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 Gesamtwurzel durchlaufen.

Eric King
quelle
Vielleicht bin ich hier verwirrende Konzepte, aber mein Verständnis des Repository-Musters besteht darin, die Persistenz von der Domäne durch Verwendung einer Standardschnittstelle für Persistenz zu entkoppeln. Wenn Sie benutzerdefinierte Methoden für ein bestimmtes Feature hinzufügen müssen, scheint dies eine erneute Kopplung zu sein.
Erik Funkenbusch
1
Der Persistenz - Mechanismus wird von der Domäne entkoppelt, aber nicht das, was beibehalten wird. Wenn Sie Dinge wie "Wir müssen die Bestellkopfzeilen hier auflisten" sagen, müssen Sie OrderHeader in Ihrer Domain modellieren und eine Möglichkeit bereitstellen, diese aus Ihrem Repository abzurufen.
Eric King
Lassen Sie sich auch nicht von der "Standardschnittstelle für Persistenz" abhalten. Es gibt kein generisches Repository-Muster, das für alle möglichen Apps ausreicht. Jede App verfügt über viele Repository-Methoden, die über die Standardmethoden "GetById", "Save" usw. hinausgehen. Diese Methoden sind der Startpunkt und nicht der Endpunkt.
Eric King
4

Meine Verwendung von DDD wird möglicherweise nicht als "reines" DDD betrachtet, aber ich habe die folgenden praktischen Strategien für die 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 Stammverzeichnis verwendet (es ist nicht öffentlich verfügbar).
  • Ein Repository kann Abfrageaufrufe enthalten (z. B. GetAllActiveOrders, GetOrderItemsForOrder).
  • Ein Service macht eine öffentliche Teilmenge des Repositorys und andere nicht-rohe Vorgänge verfügbar (z. B. Geld von einem Bankkonto auf ein anderes überweisen, LoadById, Search / Find, CreateEntity usw.).
  • Ich benutze den Root -> Service -> Repository Stack. Ein DDD-Dienst soll nur für alles verwendet werden, was eine Entität nicht selbst beantworten kann (z. B. LoadById, TransferMoneyFromAccountToAccount), aber in der realen Welt bleibe ich auch bei anderen CRUD-bezogenen Diensten (Save, Delete, Query), obwohl das root sollte diese selbst "beantworten / ausführen" können. Beachten Sie, dass es nichts Falsches ist, einer Entität Zugriff auf einen anderen aggregierten Root-Service zu gewähren! Denken Sie jedoch daran, dass Sie keinen Service (GetOrderItemsForOrder) enthalten, sondern diesen in das Repository aufnehmen würden, damit das Aggregate Root davon Gebrauch machen kann. Beachten Sie, dass ein Service keine offenen Abfragen offen legen sollte, wie dies das Repository kann.
  • Normalerweise definiere ich ein Repository abstrakt im Domänenmodell (über die Schnittstelle) und stelle eine separate konkrete Implementierung bereit. Ich definiere einen Service vollständig im Domain-Modell, indem ich ihn in ein konkretes Repository einspeise.

** Sie müssen kein gesamtes Aggregat zurückbringen. Wenn Sie jedoch mehr möchten, müssen Sie den Root fragen, nicht einen anderen Dienst oder ein anderes Repository. Dies ist verzögertes Laden und kann entweder manuell mit verzögertem Laden durch arme Leute (Injizieren des entsprechenden Repositorys / Dienstes in das Stammverzeichnis) oder mit einem ORM durchgeführt werden, der 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 einem separaten Aufruf laden möchte. Beachten Sie, dass wir mit einem "OrderHeader" tatsächlich ein zusätzliches Konzept in die Domain einführen.

Mike Rowley
quelle
3

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 Layers, den der Service Layer um das Domänenmodell umschließt und eine vereinfachte Ansicht der Geschäftsdomäne bietet (eine Projektion, wenn Sie so wollen), die es ermöglicht, das Domänenmodell nach Bedarf zu ändern ohne die Anwendungen über die Service-Schicht direkt zu beeinflussen.

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 der Auftrag für die Manipulation seiner Auftragspositionen verantwortlich.

In Ihrem Beispiel würde die Serviceebene eine Operation wie GetOrdersForCustomer verfügbar machen, die nur das zurückgibt, was zum Anzeigen einer Zusammenfassungsliste der Bestellungen (wie Sie sie OrderHeaders nennen) erforderlich ist.

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 stellen auch eine Abfrageobjektspezifikation bereit.

Ein Repository vermittelt zwischen der Domänen- und der Datenzuordnungsschicht und fungiert dabei als speicherinterne Domänenobjektsammlung. Client-Objekte erstellen deklarativ Abfragespezifikationen und senden sie zur Zufriedenheit an Repository. (von der 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. Wenn Sie also nur die Überschriften Ihrer Bestellung benötigen, können Sie eine Abfrage erstellen, die nur die Überschriften zurückgibt, und diese mithilfe einer praktischen Methode in Ihren Repositorys verfügbar machen.

Hoffe das hilft Dinge zu klären.

Michael Brown
quelle
0

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 Auflistungssemantik verwendet und im Allgemeinen im Arbeitsspeicher aufbewahrt wird. Dies bedeutet, dass ein komplettes Objektdiagramm erstellt wird.

Bewahren Sie diese Repositorys im RAM Ihres Servers auf. Sie übergeben nicht nur Objekte an die Datenbank!

Wenn ich in einer Webanwendung mit einer Seite bin, auf der Bestellungen aufgelistet sind, auf die Sie klicken können, um Details anzuzeigen, möchte ich wahrscheinlich, dass auf der Seite mit der Bestellauflistung Details zu den Bestellungen angezeigt werden (ID, Name, Betrag, Datum). um einem Benutzer bei der Entscheidung zu helfen, welchen er sich ansehen möchte.

Zu diesem Zeitpunkt haben Sie zwei Möglichkeiten.

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

  2. Sie können eine Abfrage durchführen, die alle Informationen abruft und im Cache speichert. Auf der nächsten Seitenanforderung lesen Sie den RAM des Servers anstelle der Datenbank. Wenn der Benutzer auf die nächste Seite klickt oder diese auswählt, werden immer noch keine Fahrten in die Datenbank ausgeführt.

In Wirklichkeit ist es genau das, was Sie implementieren, und das Implementierungsdetail. Wenn mein größter Benutzer 10 Bestellungen hat, möchte ich wahrscheinlich Option 2 wählen. Wenn ich über 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 zukünftig ein Ticket bekomme, um dem Benutzer zu sagen, wie viel sie im letzten Monat auf der Seite mit der Bestellauflistung für Bestellungen ( aggregierte Daten ) ausgegeben haben , würde ich lieber die Logik schreiben, um diese in SQL zu berechnen und eine weitere Hin- und Rückreise zu tätigen die DB oder möchten Sie sie lieber anhand der Daten berechnen, die sich bereits im RAM des Servers befinden?

Nach meiner Erfahrung bieten Domänenaggregate enorme Vorteile.

  • Sie sind eine riesige Wiederverwendung von Teilecodes, die tatsächlich funktioniert.
  • Sie vereinfachen den Code, indem sie Ihre Geschäftslogik direkt in der Kernebene belassen, anstatt eine Infrastrukturebene durchsuchen zu müssen, um den SQL-Server dazu zu bringen, dies zu tun.
  • Sie können Ihre Antwortzeiten auch drastisch verkürzen, indem sie die Anzahl der Abfragen reduzieren, die Sie durchführen müssen, da Sie sie einfach zwischenspeichern können.
  • Die SQL, die ich schreibe, ist oft viel besser zu warten, da ich oft nur nach allem frage und die Serverseite berechne.
WhiteleyJ
quelle