it-swarm.com.de

DDD Injecting Services für Aufrufe von Entitätsmethoden

Kurzformat der Frage

Entspricht es den Best Practices von DDD und OOP, Services in Entity-Methodenaufrufe einzufügen?

Langformatbeispiel

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 Methode getLineItems 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).

11
e_i_pi

Es ist völlig in Ordnung , einen Domänendienst in einem Entitätsaufruf zu übergeben. Angenommen, wir müssen eine Rechnungssumme mit einem komplizierten Algorithmus berechnen, der beispielsweise von einem Kundentyp abhängen kann. So könnte es aussehen:

class Invoice
{
    private $currency;
    private $customerId;

    public function __construct()
    {
    }

    public function sum(InvoiceCalculator $calculator)
    {
        $sum =
            new SumRecord(
                $calculator->calculate($this)
            )
        ;

        if ($sum->isZero()) {
            $this->events->add(new ZeroSumCalculated());
        }

        return $sum;
    }
}

Ein anderer Ansatz besteht jedoch darin, eine Geschäftslogik, die sich im Domänendienst befindet, über Domänenereignisse zu trennen. Beachten Sie, dass dieser Ansatz nur unterschiedliche Anwendungsdienste, jedoch denselben Datenbanktransaktionsbereich impliziert.

Der dritte Ansatz ist der, für den ich bin: Wenn ich einen Domain-Service benutze, bedeutet dies wahrscheinlich, dass ich ein Domain-Konzept verpasst habe, da ich meine Konzepte modelliere hauptsächlich mit Substantiven , nicht Verben. Im Idealfall bin ich brauche keinen Domain-Service überhaupt und ein großer Teil meiner gesamten Geschäftslogik befindet sich in Dekorateure .

9
Zapadlo

Ich bin schockiert, einige der Antworten hier zu lesen.

Es ist absolut gültig, Domänendienste in Entitätsmethoden in DDD zu übergeben, um einige Geschäftsberechnungen zu delegieren. Stellen Sie sich beispielsweise vor, Ihr aggregierter Stamm (eine Entität) muss über http auf eine externe Ressource zugreifen, um Geschäftslogik zu erstellen und ein Ereignis auszulösen. Wie würden Sie es sonst tun, wenn Sie den Service nicht über die Geschäftsmethode des Unternehmens einspeisen? Würden Sie einen http-Client in Ihrer Entität instanziieren? Das klingt nach einer schrecklichen Idee.

Was falsch ist, ist das Einfügen von Diensten in Aggregate über den Konstruktor. Aber durch eine Geschäftsmethode ist es in Ordnung und völlig normal.

6
diegosasw

Entspricht es den Best Practices von DDD und OOP, Services in Entity-Methodenaufrufe einzufügen?

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 IO -Aufrufe) ausführen.

$order->getLineItems($orderService)

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:

  1. Ihre Aggregatgrenzen sind falsch, sie sind zu groß.

  2. In diesem Fall verwenden Sie das Aggregat nur zum Lesen. Die beste Lösung besteht darin, das Schreibmodell vom Lesemodell zu trennen (d. H. CQRS zu verwenden). In dieser saubereren Architektur dürfen Sie nicht das Aggregat, sondern ein Lesemodell abfragen.

5

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 impliziert, 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".

Das Zurückgeben 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.

Es ist durchaus sinnvoll, einen Domänendienst als Argument zu verwenden, um das Aggregat bei der Bereitstellung der richtigen Werte zu unterstützen.

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.

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems($orderService) as $lineItem) {
  ...
}

Diese Rechtschreibung ist also seltsam, wenn wir versuchen, auf die Sammlung von Werbebuchungswerten dieser Bestellung zuzugreifen. Die natürlichere Schreibweise wäre

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

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 mehrere 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.

3
VoiceOfUnreason

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.

3
ivenxu

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 breitere Prinzip ist: 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:

$order = $orderService->getOrderByID($orderID);
foreach($order->getLineItems() as $lineItem) {
  ...
}

Tun Sie dies stattdessen

$order = $orderService->getOrderByID($orderID);
$order->doSomethingSignificant();

Beim objektorientierten Design versuchen wir zu vermeiden, in Objektdaten herumzufischen. Wir ziehen es vor, das Objekt zu bitten, das zu tun, was wir wollen.

2
xpmatteo