Kurzes Fragenformat
Gehört es zu den Best Practices von DDD und OOP, Dienste in Entitätsmethodenaufrufe einzufügen?
Langformat-Beispiel
Angenommen, wir haben den klassischen Order-LineItems-Fall in DDD, in dem wir eine Domänenentität namens Order haben, die auch als Aggregatstamm fungiert, und diese Entität besteht nicht nur aus ihren Wertobjekten, sondern auch aus einer Sammlung von Werbebuchungen Entitäten.
Angenommen, wir möchten eine fließende Syntax in unserer Anwendung, damit wir so etwas tun können (beachten Sie die Syntax in Zeile 2, in der wir die getLineItems
Methode aufrufen ):
$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
...
}
Wir möchten keine LineItemRepository in die OrderEntity einfügen, da dies eine Verletzung mehrerer Prinzipien darstellt, die mir einfallen. Aber die fließende Syntax ist etwas, das wir wirklich wollen, weil es einfach zu lesen und zu warten sowie zu testen ist.
Betrachten Sie den folgenden Code und beachten Sie die Methode getLineItems
in OrderEntity
:
interface IOrderService {
public function getOrderByID($orderID) : OrderEntity;
public function getLineItems(OrderEntity $orderEntity) : LineItemCollection;
}
class OrderService implements IOrderService {
private $orderRepository;
private $lineItemRepository;
public function __construct(IOrderRepository $orderRepository, ILineItemRepository $lineItemRepository) {
$this->orderRepository = $orderRepository;
$this->lineItemRepository = $lineItemRepository;
}
public function getOrderByID($orderID) : OrderEntity {
return $this->orderRepository->getByID($orderID);
}
public function getLineItems(OrderEntity $orderEntity) : LineItemCollection {
return $this->lineItemRepository->getLineItemsByOrderID($orderEntity->ID());
}
}
class OrderEntity {
private $ID;
private $lineItems;
public function getLineItems(IOrderServiceInternal $orderService) {
if(!is_null($this->lineItems)) {
$this->lineItems = $orderService->getLineItems($this);
}
return $this->lineItems;
}
}
Ist dies die akzeptierte Methode zur Implementierung einer fließenden Syntax in Entities, ohne die Kernprinzipien von DDD und OOP zu verletzen? Für mich scheint es in Ordnung zu sein, da wir nur die Service-Schicht verfügbar machen, nicht die Infrastruktur-Schicht (die im Service verschachtelt ist).
Nein, Sie sollten nichts in Ihre Domänenschicht einfügen (dies schließt Entitäten, Wertobjekte, Fabriken und Domänendienste ein). Diese Schicht sollte unabhängig von Frameworks, Bibliotheken oder Technologien von Drittanbietern sein und keine E / A-Aufrufe tätigen.
Dies ist falsch, da das Aggregat nichts anderes als sich selbst benötigen sollte, um die Bestellpositionen zurückzugeben. Das gesamte Aggregat sollte bereits vor seinem Methodenaufruf geladen sein. Wenn Sie der Meinung sind, dass dies faul geladen werden sollte, gibt es zwei Möglichkeiten:
Ihre Aggregatgrenzen sind falsch, sie sind zu groß.
In diesem Fall verwenden Sie das Aggregat nur zum Lesen. Die beste Lösung besteht darin, das Schreibmodell vom Lesemodell zu trennen (dh CQRS zu verwenden ). In dieser übersichtlichen Architektur dürfen Sie nicht das Aggregat, sondern ein Lesemodell abfragen.
quelle
Die Schlüsselidee in taktischen DDD-Mustern: Die Anwendung greift auf alle Daten in der Anwendung zu, indem sie auf einen aggregierten Stamm einwirkt. Dies bedeutet, dass die einzigen Entitäten, auf die außerhalb des Domänenmodells zugegriffen werden kann, die aggregierten Wurzeln sind.
Das Order-Aggregat-Stammverzeichnis würde niemals einen Verweis auf seine Lineitem-Auflistung liefern, mit dem Sie die Auflistung ändern könnten, und es würde auch keine Auflistung von Verweisen auf eine Werbebuchung liefern, mit der Sie sie ändern könnten. Wenn Sie das Auftragsaggregat ändern möchten, gilt das Hollywood-Prinzip: "Sagen, nicht fragen".
Die Rückgabe von Werten aus dem Aggregat ist in Ordnung, da Werte von Natur aus unveränderlich sind. Sie können meine Daten nicht ändern, indem Sie Ihre Kopie davon ändern.
Die Verwendung eines Domänendienstes als Argument, um das Aggregat bei der Bereitstellung der richtigen Werte zu unterstützen, ist durchaus sinnvoll.
Normalerweise würden Sie keinen Domänendienst verwenden, um Zugriff auf Daten innerhalb des Aggregats zu gewähren, da das Aggregat bereits Zugriff darauf haben sollte.
Diese Schreibweise ist also seltsam, wenn wir versuchen, auf die Sammlung von Werbebuchungswerten dieser Bestellung zuzugreifen. Die natürlichere Schreibweise wäre
Dies setzt natürlich voraus, dass die Werbebuchungen bereits geladen wurden.
Das übliche Muster ist, dass die Last des Aggregats den gesamten für den jeweiligen Anwendungsfall erforderlichen Status umfasst. Mit anderen Worten, Sie haben möglicherweise verschiedene Möglichkeiten, dasselbe Aggregat zu laden. Ihre Repository-Methoden sind zweckmäßig .
Diesen Ansatz finden Sie nicht im ursprünglichen Evans, wo er davon ausging, dass einem Aggregat ein einzelnes Datenmodell zugeordnet ist. Es fällt natürlicher aus CQRS heraus.
quelle
lineItems()
und das Vorladen beim ersten Abrufen der aggregierten Wurzel.Im Allgemeinen haben Wertobjekte, die zum Aggregat gehören, kein eigenes Repository. Es liegt in der Gesamtverantwortung von root, sie zu füllen. In Ihrem Fall liegt es in der Verantwortung Ihres OrderRepository, sowohl die Order-Entity- als auch die OrderLine-Werteobjekte zu füllen.
In Bezug auf die Infrastrukturimplementierung des OrderRepository ist ORM eine Eins-zu-Viele-Beziehung, und Sie können die OrderLine entweder eifrig oder faul laden.
Ich bin mir nicht sicher, was Ihre Dienste genau bedeuten. Es ist ziemlich nah an "Application Service". Wenn dies der Fall ist, ist es im Allgemeinen keine gute Idee, die Dienste in das aggregierte Stamm- / Entitäts- / Wertobjekt einzufügen. Der Anwendungsdienst sollte der Client des aggregierten Stamm- / Entitäts- / Wertobjekt- und Domänendienstes sein. Eine andere Sache bei Ihren Diensten ist, dass es auch keine gute Idee ist, Wertobjekte im Anwendungsdienst verfügbar zu machen. Auf sie sollte über das aggregierte Stammverzeichnis zugegriffen werden.
quelle
Die Antwort lautet: definitiv NEIN, vermeiden Sie die Übergabe von Diensten in Entitätsmethoden.
Die Lösung ist einfach: Lassen Sie das Order-Repository die Order mit all ihren LineItems zurückgeben. In Ihrem Fall lautet das Aggregat Order + LineItems. Wenn das Repository also kein vollständiges Aggregat zurückgibt, erledigt es seine Aufgabe nicht.
Das allgemeinere Prinzip lautet: Halten Sie funktionale Bits (z. B. Domänenlogik) von nicht funktionalen Bits (z. B. Persistenz) getrennt.
Noch etwas: Wenn Sie können, versuchen Sie dies zu vermeiden:
Tun Sie dies stattdessen
Beim objektorientierten Design versuchen wir zu vermeiden, in Objektdaten herumzufischen. Wir ziehen es vor, das Objekt zu bitten, das zu tun, was wir wollen.
quelle