实体和值对象永远不应该相互依赖。这些是所有内容中最基本的DDD 的构建块 http://en.wikipedia.org/wiki/Domain-driven_design#Building_blocks_of_DDD。它们代表了您的问题域的概念,因此应该关注问题。通过使它们依赖于工厂、存储库和服务,您会使焦点变得模糊。在实体和值对象中引用服务还存在另一个问题。因为服务也拥有域逻辑,所以您可能会想将域模型的一些职责委托给服务,这最终可能会导致贫血域模型 http://en.wikipedia.org/wiki/Anemic_domain_model.
工厂和存储库只是用于创建和持久化实体的助手。大多数时候,它们在实际问题领域中没有类比,因此根据领域逻辑,从工厂和存储库到服务和实体的引用是没有意义的。
关于您提供的示例,这就是我的实现方式
$article->addTag($tag);
$articleRepository->save($article);
我不会直接访问底层集合,因为我可能想要Article
在上执行一些域逻辑(施加约束,验证)Tag
在将其添加到集合之前。
避免这种情况
$articleService->addTag($article, $tag);
仅使用服务来执行概念上不属于任何实体的操作。首先,尝试将其适合实体,确保它不适合任何实体。然后才使用服务。这样你就不会得到贫乏的领域模型。
UPDATE 1
引用 Eric Evans 的《领域驱动设计:解决软件核心的复杂性》一书:
应谨慎使用服务,不得剥夺服务的权利
他们所有行为的实体和价值对象。
UPDATE 2
有人否决了这个答案,我不知道为什么。我只能怀疑其中的原因。它可能与实体和服务之间的引用有关,也可能与示例代码有关。好吧,我对示例代码无能为力,因为这是我根据自己的经验得出的意见。但是,我对参考文献部分做了更多研究,这就是我得出的结论。
我并不是唯一一个认为从实体引用服务、存储库和工厂不是一个好主意的人。我在 SO 中发现了类似的问题:
- 实体可以访问存储库吗? https://stackoverflow.com/questions/827670/is-it-ok-for-entities-to-access-repositories
- DDD - 领域模型、服务和存储库之间的依赖关系 https://stackoverflow.com/questions/756849/ddd-dependecies-between-domain-model-services-and-repositories
- DDD - 实体不能直接访问存储库的规则 https://stackoverflow.com/questions/5694241/ddd-the-rule-that-entities-cant-access-repositories-directly
- 使用服务的 DDD 实体 https://stackoverflow.com/questions/2380882/ddd-entities-making-use-of-services
还有一些关于该主题的好文章,尤其是这篇文章How not在实体中注入服务 http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities如果您迫切需要从您的实体调用名为的服务,它还提供了一个解决方案双重派遣 http://en.wikipedia.org/wiki/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/blogs/casey/archive/2009/02/17/ddd-services.aspx" at http://devlicio.us/ 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,一个用于从数据库获取行的实用程序
和创建对象,解释示例很简单,但它是not对于实际项目来说必然是一个好的设计。
如果您查看我上面提到的 DDDSample 项目的源代码,您会发现实体除了对象中的对象外没有任何引用。model
包,即实体和值对象。顺便说一句,DDDSample 项目在《领域驱动设计:解决软件核心的复杂性》一书中有详细描述...
另外,我想与您分享的另一件事是关于领域驱动设计雅虎集团 http://tech.groups.yahoo.com/group/domaindrivendesign/message/2300. This message http://tech.groups.yahoo.com/group/domaindrivendesign/message/2305讨论中引用了 Eric Evans 关于引用存储库的模型对象主题的内容。
结论
总而言之,从实体引用服务、存储库和工厂并不好。这是最被接受的意见。尽管存储库和工厂是域层的公民,但它们不是问题域的一部分。有时(例如在维基百科关于 DDD 的文章中)域服务的概念被称为纯制造 http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Pure_Fabrication这意味着类(服务)“不代表问题域中的概念”。我宁愿将工厂和存储库称为 Pure Fabrications,因为 Eric Evans 在他的书中确实谈到了有关服务概念的其他内容:
但当手术时实际上是一个重要的领域概念, A
服务构成模型驱动设计的自然组成部分。声明于
将模型作为服务,而不是作为不存在的虚假对象
实际上代表什么,独立操作不会误导
任何人。
根据上述内容,有时从您的实体调用服务可能是明智的选择。然后,您可以使用双重调度方法,这样您就不必在实体类中保留对服务的引用。
当然,总有一些人和作者一样不同意主流观点的人。从实体访问域服务 http://danhaywood.com/2010/04/30/accessing-domain-services-from-entities/文章。