エンティティメソッドコールでのDDDインジェクションサービス


11

質問の短い形式

エンティティメソッドの呼び出しにサービスを注入することは、DDDおよびOOPのベストプラクティスの範囲内ですか?

長い形式の例

DDDに従来のOrder-LineItemsケースがあるとします。ここでは、Orderと呼ばれるドメインエンティティがあり、これは集約ルートとしても機能し、そのエンティティは値オブジェクトだけでなく、ラインアイテムのコレクションからも構成されます。エンティティ。

アプリケーションで流暢な構文を使用して、次のようなことができると仮定します(getLineItemsメソッドを呼び出す2行目の構文に注目してください)。

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

OrderEntityにLineItemRepositoryを挿入したくないのは、それが考えられるいくつかの原則に違反しているためです。しかし、構文の流暢さは私たちが本当に望んでいるものです。それは、テストだけでなく、読みやすく、保守しやすいからです。

のメソッドgetLineItemsに注意して、次のコードを検討してください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;
    }
}

それは、DDDとOOPのコア原則に違反することなく、エンティティに流暢な構文を実装する方法として受け入れられていますか?インフラストラクチャレイヤー(サービス内にネストされているレイヤー)ではなく、サービスレイヤーのみを公開しているので、私には問題ないようです。

回答:


9

それはだ、完全に罰金エンティティの呼び出しでドメインサービスを渡します。たとえば、顧客のタイプなどに依存する可能性があるいくつかの複雑なアルゴリズムを使用して、請求書の合計を計算する必要があるとします。これは次のようになります。

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;
    }
}

ただし、別のアプローチは、ドメインイベントを介してドメインサービスにあるビジネスロジックを分離することです。このアプローチは、異なるアプリケーションサービスを意味しますが、データベーストランザクションのスコープは同じであることを覚えておいてください。

3番目のアプローチは、私が支持しているアプローチです。ドメインサービスを使用していることに気付いた場合、概念は主に動詞ではなく名詞でモデル化しているため、おそらくドメインの概念を見逃していることになります。したがって、理想的には、ドメインサービスはまったく必要なく、すべてのビジネスロジックの大部分はデコレータにあります。


6

ここでいくつかの答えを読んでショックを受けました。

一部のビジネス計算を委任するためにDDDのエンティティメソッドにドメインサービスを渡すことは完全に有効です。例として、ビジネスロジックを実行してイベントを発生させるために、集約ルート(エンティティ)がhttp経由で外部リソースにアクセスする必要があると想像してください。エンティティのビジネスメソッドを介してサービスを注入しない場合、他にどのように実行しますか?エンティティ内でhttpクライアントをインスタンス化しますか?それはひどい考えのように聞こえます。

間違っているのは、コンストラクタを通じてサービスを集約して注入することです。しかし、ビジネス手法を通じて、それは大丈夫で完全に正常です。


1
あなたが与えた事件がドメインサービスの責任ではないのはなぜですか?
e_i_pi 2018

1
これはドメインサービスですが、ビジネスメソッドに挿入されています。アプリケーション層は単なるオーケスト
レータです

DDDの経験はありませんが、アプリケーションサービスからドメインサービスを呼び出さないでください。ドメインサービスの検証後も、そのアプリケーションサービスを介してエンティティメソッドを呼び出し続けますか?ドメインサービスがリポジトリを介してデータベース呼び出しを実行するため、プロジェクトで同じ問題に直面しています...これで問題ないかわかりません。
Muflix

ドメインサービスはオーケストレーションする必要があります。後でアプリケーションから呼び出す場合は、なんらかの方法で応答を処理してから、何かを実行することを意味します。多分それはビジネスロジックのように聞こえます。もしそうなら、それはドメイン層に属し、アプリケーションは後で単に依存関係を解決し、それを集合体に注入します。ドメインサービスは、データベースにアクセスする実装がインフラストラクチャレイヤー(インターフェース/契約ではなく実装のみ)に属すべきリポジトリを注入した可能性があります。ユビキタス言語を説明している場合は、ドメインに属しています。
diegosasw

5

エンティティメソッドの呼び出しにサービスを注入することは、DDDおよびOOPのベストプラクティスの範囲内ですか?

いいえ、ドメインレイヤー内には何も挿入しないでください(エンティティ、値オブジェクト、ファクトリー、ドメインサービスなど)。このレイヤーは、フレームワーク、サードパーティのライブラリ、またはテクノロジーにとらわれず、IO呼び出しを行わないでください。

$order->getLineItems($orderService)

Aggregateは注文アイテムを返すためにそれ以外に何も必要としないため、これは誤りです。全体の集計では、すでにそのメソッド呼び出しの前にロードする必要があります。これを遅延ロードする必要があると思われる場合は、2つの可能性があります。

  1. 集計の境界が間違っています。大きすぎます。

  2. このユースケースでは、Aggregateを読み取り専用に使用します。最良の解決策は、書き込みモデルを読み取りモデルから分割することです(つまり、CQRSを使用します)。このよりクリーンなアーキテクチャでは、Aggregateではなく読み取りモデルをクエリすることはできません。


検証のためにデータベース呼び出しが必要な場合は、アプリケーションサービスで呼び出して結果をドメインサービスに渡すか、リポジトリをドメインサービスに注入するのではなく、直接集約ルートに渡す必要がありますか?
Muflix

1
@Muflixはい、そうです
コンスタンティンガルベヌ

3

DDDの戦術パターンにおける重要なアイデア:アプリケーションは、集約ルートに作用することにより、アプリケーション内のすべてのデータにアクセスします。これは、ドメインモデルの外部からアクセスできる唯一のエンティティが集約ルートであることを意味します。

Order集計ルートは、コレクションを変更できるラインアイテムコレクションへの参照を生成することはなく、コレクションを変更できるラインアイテムへの参照のコレクションも生成しません。Order集計を変更する場合は、ハリウッドの原則「Tell、do n't ask」が適用されます。

値は本質的に不変であるため、集計内からを返すことは問題ありません。データのコピーを変更して私のデータを変更することはできません。

集計として正しい値を提供するのを助けるために、ドメインサービスを引数として使用することは、完全に合理的なことです。

アグリゲートは既にアクセスしているはずなので、通常はドメインサービスを使用してアグリゲート内のデータへのアクセスを提供しません。

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

したがって、このオーダーのラインアイテム値のコレクションにアクセスしようとすると、そのスペルは奇妙です。より自然なスペルは

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

もちろん、これは広告申込情報が既に読み込まれていることを前提としています。

通常のパターンでは、アグリゲートのロードには、特定のユースケースに必要なすべての状態が含まれます。言い換えれば、あなたはいくつか持っていることが異なる同じ集計をロードする方法を。リポジトリメソッドは目的に適しています。

このアプローチは、元のエバンスに見られるようなものではありません。エヴァンスは、集約には単一のデータモデルが関連付けられると想定していました。それはより自然にCQRSから外れます。


これをありがとう。今では「赤い本」の約半分を読んでおり、ハリウッドの原則をインフラストラクチャ層に適切に適用することを初めて知りました。これらのすべての回答を読み直すと、どれも良い点になりますlineItems()が、Aggregate Rootを最初に取得するときのスコープとプリロードに関して、非常に重要な点がいくつかあると思います。
e_i_pi 2017年

3

一般的に、集約に属する値オブジェクトには、それ自体にはリポジトリがありません。それらを設定するのは集約ルートの責任です。あなたの場合、OrderエンティティとOrderLine値オブジェクトの両方にデータを設定するのは、OrderRepositoryの責任です。

ちなみに、ORMの場合のOrderRepositoryのインフラストラクチャ実装は、1対多の関係であり、OrderLineを積極的にロードするか、遅延ロードするかを選択できます。

サービスの正確な意味がわかりません。「アプリケーションサービス」にかなり近いです。これが事実である場合、通常、サービスを集約ルート/エンティティ/値オブジェクトに注入することはお勧めできません。アプリケーションサービスは、集約ルート/エンティティ/値オブジェクトおよびドメインサービスのクライアントである必要があります。サービスに関するもう1つのことは、Application Serviceで値オブジェクトを公開することも良い考えではありません。それらは集約ルートによってアクセスされるべきです。


2

正解は次のとおりです。エンティティメソッドでサービスを渡すことは避けてください。

解決策は簡単です。OrderリポジトリにすべてのLineItemを含むOrderを返すだけです。あなたの場合、集計はOrder + LineItemsなので、リポジトリが完全な集計を返さない場合、その仕事はしていません。

より広い原則は、機能ビット(ドメインロジックなど)を非機能ビット(永続性など)から分離することです。

もう1つ:可能であれば、これを回避するようにしてください。

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

代わりにこれを行う

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

オブジェクト指向設計では、オブジェクトデータ内でのフィッシングを回避しようとします。私たちはオブジェクトに私たちが望むことをするように依頼することを好みます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.