Szenario:
Ein Kunde gibt eine Bestellung auf und gibt nach Erhalt des Produkts eine Rückmeldung zum Bestellvorgang.
Nehmen Sie die folgenden aggregierten Wurzeln an:
- Kunde
- Auftrag
- Feedback
Hier sind die Geschäftsregeln:
- Ein Kunde kann nur Feedback zu seiner eigenen Bestellung geben, nicht zu der eines anderen.
Ein Kunde kann nur dann eine Rückmeldung geben, wenn die Bestellung bezahlt wurde.
class Feedback { public function __construct($feedbackId, Customer $customer, Order $order, $content) { if ($customer->customerId() != $order->customerId()) { // Error } if (!$order->isPaid()) { // Error } $this->feedbackId = $feedbackId; $this->customerId = $customerId; $this->orderId = $orderId; $this->content = $content; } }
Nehmen wir nun an, das Unternehmen möchte eine neue Regel:
Ein Kunde kann nur dann eine Rückmeldung geben, wenn die
Supplier
Ware der Bestellung noch in Betrieb ist.class Feedback { public function __construct($feedbackId, Customer $customer, Order $order, Supplier $supplier, $content) { if ($customer->customerId() != $order->customerId()) { // Error } if (!$order->isPaid()) { // Error } // NEW RULE HERE if (!$supplier->isOperating()) { // Error } $this->feedbackId = $feedbackId; $this->customerId = $customerId; $this->orderId = $orderId; $this->content = $content; } }
Ich habe die Implementierung der ersten beiden Regeln im Feedback
Aggregat selbst platziert. Ich fühle mich wohl dabei, insbesondere angesichts der Feedback
Tatsache , dass das
Aggregat alle anderen Aggregate nach Identität referenziert. Zum Beispiel zeigen die Eigenschaften der Feedback
Komponente an, dass sie von der Existenz der anderen Aggregate weiß
, daher fühle ich mich wohl, wenn sie auch den schreibgeschützten Zustand dieser Aggregate kennt .
Aufgrund seiner Eigenschaften hat das Feedback
Aggregat jedoch keine Kenntnis von der Existenz des
Supplier
Aggregats. Sollte es also Kenntnis vom schreibgeschützten Zustand dieses Aggregats haben?
Die alternative Lösung zur Implementierung von Regel 3 besteht darin, diese Logik an die entsprechende Stelle zu verschieben CommandHandler
. Dies scheint jedoch die Domänenlogik vom "Zentrum" meiner zwiebelbasierten Architektur zu entfernen.
Supplier
würde der Betriebszustand eines Aggregats nicht über einOrder
Repository abgefragt .Supplier
undOrder
sind zwei separate Aggregate. Zweitens gab es in der DDD / CQRS-Mailingliste eine Frage zum Übergeben von aggregierten Roots und Repositorys an andere aggregierte Root-Methoden (einschließlich des Konstruktors). Es gab verschiedene Meinungen, aber Greg Young erwähnte, dass es üblich ist, aggregierte Wurzeln als Parameter zu übergeben, während eine andere Person sagte, dass Repositories enger mit der Infrastruktur als mit der Domäne verbunden sind. ZB Repositorys "abstrakt in Speichersammlungen" und haben keine Logik.Customer
nur zu einer eigenen Bestellung Feedback geben kann ($order->customerId() == $customer->customerId()
), müssen wir auch die Lieferanten-ID ($order->supplierId() == $supplier->supplierId()
) vergleichen. Die erste Regel schützt vor dem Benutzer, der falsche Werte angibt. Die zweite Regel schützt vor dem Programmierer, der falsche Werte liefert. Die Prüfung, ob der Lieferant tätig ist, muss jedoch entweder in derFeedback
Entität oder im Befehlshandler erfolgen. Wo ist die Frage?Antworten:
Wenn für die Transaktionskorrektheit ein Aggregat den aktuellen Status eines anderen Aggregats kennt, ist Ihr Modell falsch.
In den meisten Fällen ist keine Transaktionskorrektheit erforderlich . Unternehmen neigen dazu, Latenz und veraltete Daten zu tolerieren. Dies gilt insbesondere für Inkonsistenzen, die leicht zu erkennen und leicht zu beheben sind.
Der Befehl wird also von dem Aggregat ausgeführt, das den Status ändert. Um die nicht unbedingt korrekte Prüfung durchzuführen, ist nicht unbedingt die neueste Kopie des Status des anderen Aggregats erforderlich .
Bei Befehlen für ein vorhandenes Aggregat besteht das übliche Muster darin, ein Repository an das Aggregat zu übergeben, und das Aggregat übergibt seinen Status an das Repository, das eine Abfrage bereitstellt, die einen unveränderlichen Status / eine unveränderliche Projektion des anderen Aggregats zurückgibt
Aber Konstruktionsmuster sind seltsam - wenn Sie das Objekt erstellen, kennt der Aufrufer den internen Status bereits, weil er ihn bereitstellt. Das gleiche Muster funktioniert, es sieht einfach sinnlos aus
Wir befolgen die Regeln, indem wir die gesamte Domänenlogik in den Domänenobjekten beibehalten, schützen die Geschäftsinvariante jedoch nicht wirklich auf nützliche Weise (da der Anwendungskomponente dieselben Informationen zur Verfügung stehen). Für das Erstellungsmuster wäre es genauso gut zu schreiben
quelle
SupplierOperatingQuery
Abfrage des Lesemodells oder "Abfrage" im Namen irreführend? 2. Transaktionskonsistenz ist nicht erforderlich. Es spielt keine Rolle, ob der Lieferant den Betrieb eine Sekunde vor dem Feedback eines Kunden einstellt, aber heißt das, wir sollten es trotzdem nicht überprüfen? 3. Erzwingt die Bereitstellung eines "Abfragedienstes" anstelle des Objekts in Ihrem Beispiel die Transaktionskonsistenz? Wenn das so ist, wie? 4. Wie wirkt sich die Verwendung solcher Abfragedienste auf Unit-Tests aus?Ich weiß, dass dies eine alte Frage ist, aber ich möchte darauf hinweisen, dass das Problem direkt auf einer falschen Prämisse beruht. Das heißt, die aggregierten Wurzeln, von denen wir annehmen sollen, dass sie existieren, sind einfach falsch.
In dem von Ihnen beschriebenen System gibt es nur einen aggregierten Stamm: Kunde. Sowohl eine Bestellung als auch eine Rückmeldung sind zwar eigenständige Aggregate, hängen jedoch vom Kunden ab, sind also selbst keine aggregierten Wurzeln. Die Logik, die Sie in Ihrem Feedback-Konstruktor angeben, scheint darauf hinzudeuten, dass eine Bestellung eine Kunden-ID haben muss und Feedback auch mit einem Kunden verknüpft sein muss. Das macht Sinn. Wie kann eine Bestellung oder ein Feedback nicht mit einem Kunden in Verbindung gebracht werden? Darüber hinaus scheint der Lieferant logisch mit der Bestellung verbunden zu sein (würde sich also in diesem Aggregat befinden).
Vor diesem Hintergrund sind alle gewünschten Informationen bereits im Stammverzeichnis des Kunden verfügbar, und es wird deutlich, dass Sie Ihre Regeln an der falschen Stelle durchsetzen. Konstruktoren sind schreckliche Orte zur Durchsetzung von Geschäftsregeln und sollten unter allen Umständen vermieden werden. So sollte es aussehen (Hinweis: Ich werde keine Konstruktoren für Kunde und Auftrag einschließen, da wahrscheinlich Fabriken verwendet werden sollten. Außerdem werden nicht alle Schnittstellenmethoden angezeigt).
Okay. Lassen Sie uns das ein wenig zusammenfassen. Das erste, was Sie bemerken werden, ist, wie viel aussagekräftiger dieses Modell ist. Alles ist eine Aktion, es wird klar, wo Geschäftsregeln gelten sollten. Das obige Design "macht" nicht nur das Richtige, es "sagt" das Richtige.
Was würde jemanden dazu bringen anzunehmen, dass Regeln in der folgenden Zeile ausgeführt werden?
Zweitens können Sie sehen, dass die gesamte Logik zur Validierung von Geschäftsregeln so nah wie möglich an den Modellen ausgeführt wird, auf die sie sich beziehen. In Ihrem Beispiel führt der Konstruktor (eine einzelne Methode) mehrere Validierungen für verschiedene Modelle durch. Das bricht das SOLID-Design. Wo würden wir eine Überprüfung hinzufügen, um sicherzustellen, dass der Feedback-Inhalt keine schlechten Wörter enthält? Noch eine Überprüfung im Konstruktor? Was ist, wenn verschiedene Arten von Feedback unterschiedliche Inhaltsprüfungen erfordern? Hässlich.
Drittens können Sie anhand der Schnittstellen erkennen, dass es natürliche Orte gibt, an denen die Regeln durch Komposition erweitert / geändert werden können. Beispielsweise können für verschiedene Arten von Bestellungen unterschiedliche Regeln gelten, wann Feedback gegeben werden kann. Die Bestellung kann auch verschiedene Arten von Rückmeldungen liefern, die wiederum unterschiedliche Regeln für die Validierung haben können.
Sie können auch eine Reihe von ICustomer * -Schnittstellen sehen. Diese werden verwendet, um das Kundenaggregat zusammenzustellen, das wir hier benötigen (wahrscheinlich nicht nur als Kunde bezeichnet). Der Grund dafür ist einfach. Es ist SEHR wahrscheinlich, dass ein Kunde ein RIESIGES aggregiertes Stammverzeichnis ist, das sich über Ihre gesamte Domain / Datenbank verteilt. Durch die Verwendung von Schnittstellen können wir dieses eine Aggregat (das wahrscheinlich zu groß zum Laden ist) in mehrere Aggregatwurzeln zerlegen, die nur bestimmte Aktionen (z. B. Bestellen oder Feedback) bereitstellen. Sie können sehen, dass das Aggregat in meiner Implementierung BEIDE Bestellungen aufgeben UND Feedback geben kann, aber nicht zum Zurücksetzen eines Passworts oder zum Ändern eines Benutzernamens verwendet werden kann.
Die Antwort auf Ihre Frage lautet also, dass sich Aggregate selbst validieren sollten. Wenn sie es nicht können, haben Sie wahrscheinlich ein mangelhaftes Modell.
quelle