Dependency injection 域驱动设计和IoC/依赖注入
我现在正在尝试应用我学到的关于DDD的知识,我对域模型中的依赖关系流有点困惑 我的问题是:Dependency injection 域驱动设计和IoC/依赖注入,dependency-injection,dependencies,domain-driven-design,inversion-of-control,Dependency Injection,Dependencies,Domain Driven Design,Inversion Of Control,我现在正在尝试应用我学到的关于DDD的知识,我对域模型中的依赖关系流有点困惑 我的问题是: 实体是否应该了解域中的工厂、存储库和服务 存储库是否应该知道域中的服务 另一件困扰我的事情是,当我想向集合添加和实体时,如何处理集合 假设我正在开发一个简单的CMS。在CMS中,我有一个包含标记实体的文章实体和标记集合 现在,如果我想添加一个带有新标记的关系。有什么更好的方法?(PHP中的示例) 或者我可以通过服务来完成 $articleService->addTag($article, TagEn
$articleService->addTag($article, TagEntity);
你觉得怎么样
谢谢。我会回答这个问题,前提是我认为没有正确的答案,只是不同的方法 从领域对象的角度考虑,我认为第一种方法是DDD。您纯粹是在处理域对象 但是,我确实认为服务对象可以将其作为API/服务层的一部分公开。在这种情况下,您可以将#1中的代码包装到服务#2中。这允许您避免将域对象暴露给外部使用者,并且您可以在更新域模型时保持外部接口/API不变 然而,这只是一种观点 实体是否应该了解工厂、存储库和服务 域名
实体不应引用存储库或应用程序服务。如果实体使用工厂创建组成实体,则实体可以引用工厂。如果实体将某个域服务用于某些行为,则该实体对该域服务具有依赖性也是可以接受的 存储库是否应该知道域中的服务 通常不会。存储库应该只负责持久性 现在,如果我想添加一个带有新标记的关系。会是什么 更好的方法是什么 这取决于您所指的图层。在典型的DDD体系结构中,您将拥有两段代码。您将拥有一个文章应用程序服务,它封装了域,并提供了一个细粒度方法,如
addTag
,您可以在其中传递文章ID和标记ID。此方法将检索适当的文章和标记实例(如果需要),然后:
$article->tags->add(TagEntity);
$articleRepository->save($article);
依赖于此域的所有外层都将通过应用程序服务与之通信。实体和值对象不应相互依赖,除非彼此相互依赖。这些都是最基本的问题。它们代表了问题域的概念,因此应该关注问题。通过使它们依赖于工厂、存储库和服务,您可以模糊其关注点。在实体和值对象中引用服务还有另一个问题。因为服务也拥有域逻辑,所以您可能会尝试将域模型的一些职责委托给服务,这最终可能导致 工厂和存储库只是用于创建和持久化实体的助手。大多数情况下,它们只是在实际问题域中没有类比,因此根据域逻辑,从工厂和存储库引用服务和实体是没有意义的 关于您提供的示例,我将这样实现它
$article->addTag($tag);
$articleRepository->save($article);
我不会授予对底层集合的直接访问权,因为我可能希望文章
在将标记添加到集合之前对其执行一些域逻辑(强制约束、验证)
避免这种情况
$articleService->addTag($article, $tag);
仅使用服务执行概念上不属于任何实体的操作。首先,尝试将其与实体相匹配,确保它不适合任何实体。然后才为它使用服务。这样你就不会有贫血的领域模型
更新1
引用Eric Evans的《领域驱动设计:解决软件核心的复杂性》一书:
应明智地使用服务,不允许剥离
实体和值对象的所有行为
更新2
有人否决了这个答案,我不知道为什么。我只能怀疑原因。它可以是关于实体和服务之间的引用,也可以是关于示例代码。嗯,我不能对示例代码做太多,因为这是我根据自己的经验得出的意见。但是,我在参考文献部分做了更多的研究,下面是我的想法
我不是唯一一个认为从实体引用服务、存储库和工厂不是一个好主意的人。我在SO中发现了类似的问题:
也有一些关于这个主题的好文章,特别是这篇文章,它还提供了一个解决方案,如果您迫切需要从您的实体调用一个名为的服务。下面是文章移植到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)
);
}
}
因此,正如您所看到的,Message
实体中没有对MailService
服务的引用,而是将其作为参数传递给实体的方法。本文作者在“评论”一节中提出了相同的解决方案
我还试着看看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,一个用于从数据库获取行的实用程序
创建对象对于解释示例来说很简单,但它不是
N
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);
}
}
$article = new Article();
$article->addTag($tag);
$articleRepository->save($article);
$articleService->addTag($article, TagEntity);
$article->tags->add(TagEntity);