Domain Driven Design und domänenübergreifende Interaktion

10

Ich bin ein relativer DDD-Neuling, aber ich lese alles, was ich in die Hände bekommen kann, um mein Wissen auszukochen und zu destillieren.

Ich bin auf diese DDD-Frage gestoßen, und eine der Antworten hat mich fasziniert.

DDD-begrenzte Kontexte und Domänen?

In einer der Antworten gibt das Poster das Beispiel eines E-Commerce-Systems mit Produkten in mindestens zwei Bereichen:

1) Produktkatalog 2) Bestandsverwaltung

OK, das alles macht Sinn, dh in Ihrem E-Commerce-Frontend sind Sie daran interessiert, die Produktinformationen anzuzeigen, und nicht an der Bestandsverwaltung.

ABER. Möglicherweise möchten Sie den Lagerbestand auf der Webseite anzeigen oder die Editionsnummer des vorrätigen Inventars anzeigen (stellen Sie sich vor, Ihr Inventar besteht aus Büchern, Zeitschriften usw.). Diese Informationen stammen aus der Inventardomäne.

Wie würden Sie damit umgehen? Würdest du

a) Laden Sie sowohl die Produktdomäne als auch die Inventardomäne. b) Würden Sie einige Eigenschaften Ihrer Produktdomänenentität für die Anzahl auf Lager und die Edition auf Lager halten und dann Domänenereignisse verwenden, um diese zu aktualisieren, wenn die Inventarentität aktualisiert wird?

Eine letzte Frage. Ich weiß, wir sollen die Persistenz der Domain vergessen / ignorieren und nur an die Domain denken. Aber um dies zu überdenken, würden wir im obigen Beispiel möglicherweise 2 DB-Tabellen für den Produktkatalog und den Produktbestand haben. Verwenden wir in diesen dieselbe Kennung wie für dasselbe Produkt? Oder könnten wir 1 Tabelle und 1 Tabellenzeile für die Daten verwenden und die relevanten Daten einfach den Aggregateigenschaften zuordnen?

PendorPaul
quelle

Antworten:

8

Möglicherweise möchten Sie den Lagerbestand auf der Webseite anzeigen oder die Editionsnummer des vorrätigen Inventars anzeigen (stellen Sie sich vor, Ihr Inventar besteht aus Büchern, Zeitschriften usw.). Diese Informationen stammen aus der Inventardomäne.

An dieser Stelle ist vor allem zu beachten, dass es sich um eine Ansicht handelt, dh, die Verwendung veralteter Daten ist akzeptabel.

Davon abgesehen müssen Sie nicht mit den Aggregaten interagieren (die dafür verantwortlich sind, dass Änderungen nicht gegen die Geschäftsinvariante verstoßen), sondern mit einer Darstellung einer aktuellen Kopie des Status des Aggregats.

Was ich normalerweise erwarten würde, ist eine Abfrage, die für den Produktkatalog ausgeführt wird, und eine weitere, die für das Inventar ausgeführt wird, und etwas, um die beiden in dem DTO zusammenzufassen, das Sie zur Unterstützung der Ansicht benötigen.

Laden Sie sowohl die Produktdomänen- als auch die Inventardomänenaggregate?

Das ist also nah . Wir müssen die Aggregate nicht laden, da wir nichts ändern werden. Aber wir brauchen ihren Zustand; also könnten wir das laden. Trotzdem würde ich normalerweise erwarten, dass die beiden Domänen in unterschiedlichen Prozessen ausgeführt werden. Daher würden wir beide anrufen und nicht beide laden.

Würden Sie einige Eigenschaften Ihrer Produktdomänenentität für die Anzahl auf Lager und die Edition auf Lager halten und dann Domänenereignisse verwenden, um diese zu aktualisieren, wenn die Inventarentität aktualisiert wird?

"Überquere nicht die Bäche. Es wäre schlecht."

Verwenden von Ereignissen zum Koordinieren von Informationen über Domänenkontexte hinweg: großartige Idee. Konzepte, die zu einer Domäne gehören, in eine andere verschieben: Gegenteil einer großartigen Idee, außer mehr.

Sie möchten die Domains sauber halten. Die Anwendungen , die mit den Domänen interagieren, sind nicht so wichtig. So ist es beispielsweise sinnvoll, dass die Inventaranwendung einen Dienst in der Produktanwendung aufruft, um einige produktspezifische Konzepte abzufragen, die einer Ansicht hinzugefügt werden sollen. Oder umgekehrt.

Ich kenne keinen Grund, warum eine einzelne Anwendung auf eine einzelne Domäne beschränkt werden muss. Solange es eine einzige Quelle der Wahrheit gibt, können Sie die Transaktionen nach Belieben verteilen.

Aber um dies zu überdenken, würden wir im obigen Beispiel möglicherweise 2 DB-Tabellen für den Produktkatalog und den Produktbestand haben. Verwenden wir in diesen dieselbe Kennung wie für dasselbe Produkt?

Das wäre der einfache Weg. In größeren Begriffen verwenden Sie denselben Bezeichner, da die reale Entität dieselbe ist. Die zwei verschiedenen begrenzten Kontexte modellieren diese Entität unterschiedlich, aber das Modell ist nicht die Entität der realen Welt.

Wenn das nicht funktioniert, benötigen Sie eine Abfrage, um die Lücke zu schließen. Ich denke, die häufigste Variante ist, dass die neuere Entität die ID der älteren Entität beibehält. Sie werden dies auch in einem einzigen BC sehen: Bewerber werden, wenn sie genehmigt werden, Kunden. Es ist ein anderes Aggregat (der einem Kunden zugeordnete Staat unterliegt einer anderen Invariante als der des Antragstellers); Wenn Ihre Persistenzschicht Ereignisströme verwendet, benötigt der Stream für das neue Aggregat eine andere Kennung. Es wird also irgendwo einen Staat geben, der besagt, dass "dieser Antragsteller dieser Kunde geworden ist".

Oder könnten wir 1 Tabelle und 1 Tabellenzeile für die Daten verwenden und die relevanten Daten einfach den Aggregateigenschaften zuordnen?

YIKES! Nein, tu das nicht. Sie fügen Transaktionskonflikte ohne geschäftlichen Grund hinzu.

VoiceOfUnreason
quelle
Ich habe dies als Antwort angekreuzt, danke auch an @guillaume unten, der auch darauf hingewiesen hat, dass das Lesen von Daten zur Anzeige in einer Ansicht kein Laden der Aggregate erfordert. Vielen Dank für eine so lange detaillierte Antwort. Ausgehend vom ersten Ansatz des "traditionellen" Datenmodells fiel es mir schwer, die Persistenzschicht zu vergessen und mich auf die Domänensprache zu konzentrieren. Gerade als ich denke, dass ich es habe, lese ich einen anderen Artikel, der mein Verständnis sprengt.
PendorPaul
Ich habe gerade diesen Artikel msdn.microsoft.com/en-us/magazine/dn802601.aspx gelesen, in dem detailliert beschrieben wird, wie mithilfe von Domänenereignissen einige Daten zwischen Kontexten dupliziert werden. Das Beispiel ist das Teilen einer Kundenliste zwischen dem Kundendienst und einem Auftragsabwicklungssystem. Dies dupliziert Kundendaten im Bestellsystem und verwendet Ereignisse, um die Daten zu synchronisieren. Dies steht im Widerspruch zu dem, was wir hier beantwortet haben. Sicherlich benötigt der Bestellkontext im verknüpften Beispiel nur eine Kunden-ID, die aus der Anwendung ausgefüllt werden kann, die Zugriff auf den Kundendienstkontext hat.
PendorPaul
Hier möchten Sie sehr präzise denken. Das Kopieren von Daten zwischen Systemen ist NICHT dasselbe wie das Kopieren von Daten zwischen Domänenmodellen. Jedes Mal, wenn Sie "schreibgeschützt [murmeln]" sehen, ist dies ein großer Hinweis darauf, dass die Daten nicht zu dieser Domäne gehören (auch wenn sich die Anwendung möglicherweise noch darum kümmert).
VoiceOfUnreason
Ja, das habe ich auch gedacht. Die Verschiebung des Denkprozesses ist schwierig genug, ohne dass "Experten" Artikel veröffentlichen, die das Wasser trüben. Ich habe heute meine Domain wirklich analysiert, um zu versuchen, wirklich vollständig zu verstehen, wo meine BC- und Aggregate Roots liegen. Ich bin so ziemlich da, aber ich weiß auch, dass ich mein Modell im Laufe der Zeit verfeinern und umgestalten kann. Ich bin seit Tagen in der Hölle der Analyse gefangen und habe Angst, Code zu schreiben, weil ich befürchte, dass ich DDD nicht direkt im Kopf habe. Ich denke, es ist am besten, weiterzumachen und meinen Code weiter in Frage zu stellen und ob das Modell richtig ist. Ich werde dort hinkommen. Vielen Dank
PendorPaul
Vielleicht habe ich falsch verstanden, aber die Praxis, mit der Sie meiner Meinung nach sprechen, heißt ereignisgesteuerte Zustandsübertragung (ereignisgesteuerte Zustandsübertragung) und kann ein sehr leistungsfähiges Modell sein, insbesondere für Dashboard- / Tabellenansichten, in denen Sie Daten filtern und seiten müssen über Dienste. Sie können "Projektionen" (im Grunde Ansichten) aus Domänenereignissen erstellen, um diese Dashboards zu steuern. Ich habe es vorher ziemlich erfolgreich benutzt. Es kann im richtigen Szenario ein sehr mächtiges Werkzeug sein. IMHO ist hier wichtiger, dass Sie erkennen, dass diese Projektionen keine Domänenmodelle darstellen und keine Geschäftslogik enthalten sollten.
Jordanien
3

Ich denke, Ihre Frage erfordert wirklich 2 orthogonale Sätze von Optionen -

  • Laden Sie zwei Objekte und präsentieren Sie ihre Daten zusammen oder laden Sie ein Objekt, das alles enthält, was Sie wollen?

  • Verwenden Sie Aggregate zum Anzeigen von Inhalten oder etwas anderem?

Wenn Sie an den CQRS-Ansatz glauben, stellt sich heraus, dass Aggregate möglicherweise nicht die beste Wahl für Lesevorgänge sind. Jedes Mal, wenn Sie ein Aggregat laden, um seine Daten anzuzeigen oder zu ändern, fügen Sie Ihrem System Parallelität und Konflikte hinzu. Außerdem sind Aggregate möglicherweise sperriger und langsamer zu laden als bei Verwendung von Ad-hoc-Lesemodellen, die auf die Anzeige zugeschnitten sind.

Lösung a) aus Ihrem Q scheint vielen dieser Fallstricke zu unterliegen. Option b) kann gültig sein, aber ich würde sie nur verwenden, wenn Daten aus dem InventoryManagementBC benötigt werden, um Invarianten beim Mutieren des ProductAggregats zu erzwingen . Es ist besser, wenn ein Aggregat alle Daten enthält, die zur Überprüfung seiner Geschäftsregeln bei Änderungen erforderlich sind, aber auf der Leseseite können sie überall sitzen.

In Bezug auf Daten wird häufig empfohlen, Bounded Contexts eine eigene Datenbank zuzuweisen (aus Gründen der Bereitstellbarkeit und des SoC). Sie müssen wahrscheinlich dieselben Bezeichner verwenden, wenn Sie Produkte zwischen den beiden BCs abgleichen möchten.

Informationen zu BC-übergreifenden Interaktionen finden Sie möglicherweise auch unter /programming/16713041/communicating-between-two-bounded-contexts-in-ddd

guillaume31
quelle
1
OK, das hilft. Im Wesentlichen verwenden wir Aggregate Roots und BC, um sicherzustellen, dass unsere Invarianten konsistent sind, unsere Daten gültig sind und die Operationen, die wir ausführen möchten, in unserem Aggregat oder BC zusammengefasst sind. Wenn es darum geht, diese Daten zu lesen und anzuzeigen, können wir diese separat und leicht handhaben, ohne dass alle Aggregate / BC hydratisiert werden. Warum müssen wir das Aggregat laden, wenn wir nur die Daten entweder in einem Bericht oder auf dem Bildschirm anzeigen? Das macht durchaus Sinn. Vielen Dank.
PendorPaul
Hier strahlt CQRS wirklich wirklich. Sie können einfach eine SQL-Abfrage durchführen, die Tabellen verknüpfen und die angepasste Abfrage für eine bestimmte Ansicht zurückgeben. Außerdem wird Ihr Repo von der Abfrage der Abfragemethode befreit. Sie können auch Daten in Diensten wie ElasticSearch replizieren und danach abfragen.
keine Rolle am
1

DDD ist für Anwendungen gedacht, bei denen die Geschäftslogik komplex ist. "etwas drucken" ist keine komplexe Geschäftslogik. Es ist eigentlich überhaupt keine Geschäftslogik.

Wenn die Geschäftslogik in einem Kontext einige Informationen benötigt, um einen Anwendungsfall richtig zu behandeln, sind diese Informationen Teil dieses Kontexts. Die Idee, dass ein begrenzter Kontext möglicherweise Informationen benötigt, die in einem anderen begrenzten Kontext verfügbar sind, ist daher nicht sinnvoll, da der begrenzte Kontext alle benötigten Informationen enthält.

Euphorisch
quelle
OK, nehmen Sie so etwas wie Amazon, das ist ein komplexes System mit komplexer Geschäftslogik. Sie haben Katalogverwaltung und Bestandsverwaltung. Sie benötigen keine Inventarsummen, um den Katalog zu verwalten. Damit meine ich den Produktnamen, die Beschreibung, den Typ, den Zustand usw. Sie zeigen jedoch die Anzahl der auf Lager befindlichen Artikel auf ihrer Store-Startseite an. In diesem Szenario, in dem Sie die Domänen Produktkatalogverwaltung und Produktinventar trennen möchten, aber auf Ihrer Produktseite einige Informationen zum Inventar anzeigen müssen, wie gehen Sie vor?
PendorPaul
Ich denke, was ich damit sagen will, ist, dass die Produktkatalogdomäne alle Informationen enthält, die sie zur Verwaltung des Produktkatalogs benötigt. Die Inventardomäne verfügt über alle Informationen, die zum Verwalten des Inventars erforderlich sind. Wenn ich einem Benutzer jedoch einige Informationen anzeigen möchte und diese Daten aus zwei Domänen stammen, wo mache ich das? Lade ich einfach beide Domänen in meine Benutzeroberfläche und binde die Eigenschaften, die ich anzeigen möchte? Oder habe ich einen Berichterstellungs- / Lesemechanismus, bei dem ich einen anonymen Typ oder ein DTO zurückgebe, das die Daten enthält, die ich für meine Benutzeroberfläche benötige?
PendorPaul
Es ist komisch, auf meine alten Kommentare vor 11 Monaten zurückzublicken, aber es scheint ein Leben lang zu sein. Sie hatten völlig Recht Euphorisch in Bezug auf den Lese- und Schreibaspekt. Die Anwendung, an der ich arbeite, hat sich mit meinem Verständnis ziemlich weiterentwickelt. Ich verwende jetzt CQRS-Methoden über Jimmy Bogards Mediatr. Die Flexibilität, Befehle zu haben, die mit Aggregaten interagieren, aber dann Abfragen und Abfragehandler verwenden, um alles zurückzubringen, was ich für die Anzeige benötige, ist unglaublich. Wickeln Sie das in Ansichten ein, die diese QueryHandler aufrufen, und die Entkopplung ist mit dieser gut. Danke
PendorPaul
@PendorPaul, ich glaube ich bin dort, wo du vor 11 (jetzt 13) Monaten warst. Was hat dich dahin gebracht, wo du jetzt bist?
Greg Bell
1
@ GregBell Sie müssen eine Änderung der Denkweise erzwingen. Ich steckte im Ansatz fest, "eine Datenbank zu entwerfen, eine Datenebene zu erstellen, eine Geschäftslogik zu erstellen ... usw.". Und ich habe mich darauf konzentriert, alle umfassenden Einheiten zu schaffen. dh ein "Produkt" auf einer E-Commerce-Website, das alles von Preisgestaltung, Beschreibung, Inventar, Lagerort ... abwickelt, aber das wird äußerst komplex. Der gebundene Kontextansatz bedeutet, dass im Bestandskontext das "Produkt" nur Informationen und Verhalten für die Bestandsverwaltung enthält. Beschreibung und Bilder werden in einem Inhaltskontext verwaltet, die Preisgestaltung im Preiskontext.
PendorPaul
1

Aus meiner Sicht gibt es unterschiedliche Definitionen von "Produkt" - jeder Begrenzungskontext hat seine eigene Definition der "Produkt" -Domäne:

  • Im Content-Management-Bounding-Kontext hat ein Produkt ein Bild und einen Beschreibungstext.
  • Im Inventar-Bounding-Kontext hat ein Produkt Lagermengen, Produktverkäufer und Prognosen, wann das Produkt verfügbar sein wird
  • Im Kontext der Preisberechnung gibt es Regeln, wie viel ein Produkt pro Menge kosten darf.

Darüber hinaus würde ich einen zusätzlichen Shop-Bounding-Kontext mit eigener Produktdefinition hinzufügen (eine relevante Kombination der Produktdomänen der anderen Bounding-Kontexte).

Ein Shop-Produkt hätte "Bild und Beschreibungstext" aus Inhalt und Verfügbarkeit aus "Inventar", aber nicht "Produktverkäufer" aus Inventar.

Dieser zusätzliche Shop-Bounding-Kontext hängt vom Inhalt, dem Inventar und dem Preis des Bounding-Kontexts ab

k3b
quelle
Wie erstellen Sie dieses Shop-Produkt BC? Haben Sie in diesem Kontext einen Verweis auf das Produkt- und Inventar-BC, und hydratisieren Sie diese aus Ihrem Persistenzspeicher, wenn Sie ein Store-Produkt-BC laden, und bieten Sie dann die gewünschten Eigenschaften dieser BCs über Ihre StoreProduct-Eigenschaften an? Ich habe diesen Artikel tatsächlich so gefunden, wie ich es mir vorgestellt habe: BC-Ereignisse überqueren, aber @ guillaume13 oben hat darauf hingewiesen, dass ich zu Anzeigezwecken den BC vermeiden und einfach die Daten zurückziehen kann, die ich für meine Ansicht benötige. msdn.microsoft.com/en-us/magazine/dn802601.aspx
PendorPaul