エンティティと値オブジェクトは、相互に依存することを除いて、何にも依存するべきではありません。これらは、 DDD のすべてのビルディング ブロックの中で最も基本的なものです。それらは問題領域の概念を表しているため、問題に焦点を当てる必要があります。それらをファクトリー、リポジトリ、およびサービスに依存させることで、その焦点をぼかすことができます。エンティティと値オブジェクトでサービスへの参照を持つことには、別の問題があります。サービスもドメイン ロジックを所有しているため、ドメイン モデルの責任の一部をサービスに委譲したくなり、最終的に貧血ドメイン モデルにつながる可能性があります。
ファクトリとリポジトリは、エンティティの作成と永続化に使用される単なるヘルパーです。ほとんどの場合、実際の問題ドメインに類推がないため、ファクトリとリポジトリからサービスとエンティティへの参照を持つことは、ドメイン ロジックによると意味がありません。
あなたが提供した例に関して、これは私がそれを実装する方法です
$article->addTag($tag);
$articleRepository->save($article);
基になるコレクションへの直接アクセスは許可しません。コレクションに追加する前にArticle
、ドメイン ロジック (制約の適用、検証) を実行する必要があるからです。Tag
これを避ける
$articleService->addTag($article, $tag);
サービスは、概念的にどのエンティティにも属さない操作を実行する場合にのみ使用してください。まず、それをエンティティに適合させようとし、どのエンティティにも適合しないことを確認します。その場合にのみ、サービスを使用します。この方法では、貧血ドメイン モデルで終わることはありません。
更新 1
Eric Evans の「Domain-Driven Design: Tackling Complexity in the Heart of Software」の本からの引用:
サービスは慎重に使用する必要があり、ENTITIES と VALUE OBJECTS のすべての動作を取り除くことはできません。
更新 2
誰かがこの回答に反対票を投じましたが、その理由はわかりません。理由を疑うしかありません。エンティティとサービスの間の参照に関するものであったり、サンプル コードに関するものであったりします。サンプル コードについては、私自身の経験に基づいた私の意見であるため、多くのことを行うことはできません。しかし、私は参照部分についてさらに調査を行い、ここに私が思いついたものがあります.
エンティティからサービス、リポジトリ、ファクトリを参照するのは得策ではないと考えているのは私だけではありません。SOで同様の質問を見つけました:
この主題に関するいくつかの優れた記事もあります。特に、このHow not to inject services in entitiesは、 Double Dispatchという名前のエンティティからサービスを必死に呼び出す必要がある場合の解決策も示しています。PHP に移植された記事の例を次に示します。
interface MailService
{
public function send($sender, $recipient, $subject, $body);
}
class Message
{
//...
public function sendThrough(MailService $mailService)
{
$subject = $this->isReply ? 'Re: ' . $this->title : $this->title;
$mailService->send(
$this->sender,
$this->recipient,
$subject,
$this->getMessageBody($this->content)
);
}
}
MailService
したがって、ご覧のとおり、エンティティ内のサービスへの参照はありませんMessage
。代わりに、エンティティのメソッドに引数として渡されます。この記事「DDD: サービス」の著者 ( http://devlicio.us/のコメント セクション) によって、同じ解決策が提案されています。
また、Eric Evans が著書「ドメイン駆動設計: ソフトウェアの心臓部における複雑さへの取り組み」でこれについて述べていることも調べてみました。簡単に検索したところ、正確な答えは見つかりませんでしたが、エンティティが実際にサービスを静的に呼び出す例を見つけました。つまり、エンティティへの参照はありません。
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
// Omit constructors, etc.
public Customer getCustomer() {
String sqlQuery =
"SELECT * FROM CUSTOMER WHERE" +
"SS_NUMBER = '" + customerSocialSecurityNumber + "'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Set getInvestments() {
String sqlQuery =
"SELECT * FROM INVESTMENT WHERE" +
"BROKERAGE_ACCOUNT = '" + accountNumber + "'";
return QueryService.findInvestmentsFor(sqlQuery);
}
}
そして、下のメモには次のように記載されています。
注: データベースから行をフェッチしてオブジェクトを作成するためのユーティリティである QueryService は、例を説明するのは簡単ですが
、実際のプロジェクトでは必ずしも適切な設計ではありません。
上記の DDDSample プロジェクトのソース コードを見ると、エンティティはmodel
パッケージ内のオブジェクト、つまりエンティティと値オブジェクト以外には何も参照していないことがわかります。ちなみに、DDDSampleプロジェクトは「ドメイン駆動設計:ソフトウェアの中心にある複雑さへの取り組み」という本で詳しく説明されています...
また、あなたと共有したいもう 1 つのことは、
domaindrivendesign Yahoo Groupでの同様の議論です。ディスカッションからのこのメッセージは、リポジトリを参照するモデル オブジェクトに関する Eric Evans の言葉を引用しています。
結論
要約すると、エンティティからサービス、リポジトリ、およびファクトリへの参照を持つことは適切ではありません。これは最も受け入れられている意見です。リポジトリとファクトリはドメイン層の市民ですが、問題のドメインの一部ではありません。場合によっては (たとえば、DDD に関するウィキペディアの記事で) ドメイン サービスの概念はPure Fabricationと呼ばれ、クラス (サービス) が「問題のドメインの概念を表していない」ことを意味します。Eric Evans は彼の著書でサービスの概念について別のことを言っているので、ファクトリとリポジトリを純粋なファブリケーションと呼びたいと思います。
しかし、操作が実際に重要なドメイン概念である場合、サービスはモデル駆動設計の自然な部分を形成します。実際には何も表さない偽のオブジェクトとしてではなく、サービスとしてモデルで宣言されているため、スタンドアロン操作は誰も誤解を招くことはありません。
上記によると、あなたのエンティティからサービスを呼び出すことは、時には正気なことかもしれません。次に、エンティティ クラスでサービスへの参照を保持する必要がないように、ダブル ディスパッチ アプローチを使用できます。
もちろん、エンティティからのドメイン サービスへのアクセスに関する記事の著者のように、主流の意見に反対する人も常にいます。