实体和值对象不应该相互依赖。这些是DDD 所有构建块中最基本的。它们代表了您的问题域的概念,因此应该关注问题。通过使它们依赖于工厂、存储库和服务,您会使焦点变得模糊。在实体和值对象中引用服务还有另一个问题。因为服务也拥有领域逻辑你会倾向于将领域模型的一些职责委托给服务,这最终可能导致贫血的领域模型。
工厂和存储库只是用于创建和持久化实体的助手。大多数时候,它们只是在真正的问题域中没有类比,因此根据域逻辑,从工厂和存储库引用服务和实体是没有意义的。
关于您提供的示例,这就是我将如何实现它
$article->addTag($tag);
$articleRepository->save($article);
我不会直接访问底层集合,因为我可能希望在将其添加到集合之前Article
对其执行一些域逻辑(施加约束、验证) 。Tag
避免这种情况
$articleService->addTag($article, $tag);
仅使用服务来执行概念上不属于任何实体的操作。首先,尝试将它与实体相匹配,确保它不适合任何实体。然后才使用服务。这样你就不会得到贫血的域模型。
更新 1
引用 Eric Evans 的“领域驱动设计:解决软件核心中的复杂性”一书的话:
应该明智地使用服务,并且不允许剥夺实体和价值对象的所有行为。
更新 2
有人对这个答案投了反对票,我不知道为什么。我只能怀疑原因。它可能与实体和服务之间的引用有关,也可能与示例代码有关。好吧,我对示例代码无能为力,因为这是我根据自己的经验得出的看法。但是,我对参考资料部分做了更多研究,这就是我想出的。
我不是唯一一个认为从实体引用服务、存储库和工厂不是一个好主意的人。我在这里发现了类似的问题:
还有一些关于这个主题的好文章,特别是这篇How not to injection services in entity也提供了一个解决方案,如果你迫切需要从你的实体调用一个名为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 是一个用于从数据库中获取行并创建对象的实用程序,用于解释示例很简单,但对于实际项目而言,它不一定
是一个好的设计。
model
如果您查看我上面提到的 DDDSample 项目的源代码,您会发现 Entities 除了包中的对象(即实体和值对象)之外没有任何引用。顺便说一句,DDDSample 项目在《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中有详细描述……
另外,我想与您分享的另一件事是关于
domaindrivendesign Yahoo Group的类似讨论。讨论中的这条消息引用了 Eric Evans 关于模型对象引用存储库的主题。
结论
总而言之,从实体中引用服务、存储库和工厂并不好。这是最被接受的意见。尽管存储库和工厂是域层的公民,但它们不是问题域的一部分。有时(例如,在有关 DDD 的 Wikipedia 文章中)域服务的概念被称为Pure Fabrication,这意味着类(服务)“不代表问题域中的概念”。我宁愿将工厂和存储库称为 Pure Fabrications,因为 Eric Evans 在他的书中确实提到了关于服务概念的其他内容:
但是当操作实际上是一个重要的领域概念时,服务就形成了模型驱动设计的自然部分。在模型中声明为服务,而不是作为实际上不代表任何东西的虚假对象,独立操作不会误导任何人。
根据上面所说,有时从您的实体调用服务可能是一件明智的事情。然后,您可以使用 Double Dispatch 方法,这样您就不必在您的实体类中保留对服务的引用。
当然,总有一些人不同意主流观点,比如Accessing Domain Services from Entities一文的作者。