tou*_*ano 13 dependencies domain-driven-design dependency-injection inversion-of-control
我现在正在尝试应用我对DDD的了解,我对Domain模型中依赖关系的流程有点困惑.
我的问题是:
困扰我的另一件事是当我想要添加集合并将实体添加到集合时如何处理集合.
假设我正在开发一个简单的CMS.在CMS中,我有一个包含标签实体的文章实体和标签集合.
现在,如果我想添加一个新标签的关系.有什么更好的方法呢?(PHP中的示例)
$article->tags->add(TagEntity);
$articleRepository->save($article);
Run Code Online (Sandbox Code Playgroud)
或者我可以用服务来做.
$articleService->addTag($article, TagEntity);
Run Code Online (Sandbox Code Playgroud)
你怎么看?
谢谢.
zaf*_*aja 21
实体和值对象永远不应该依赖于彼此之外的任何东西.这些是DDD所有构建模块中最基本的.它们代表了您的问题域的概念,因此应该关注问题.通过使它们依赖于工厂,存储库和服务,您可以使焦点变得模糊.引用实体和值对象中的服务还有另一个问题.因为服务还拥有域逻辑您将很想将域模型的一些职责委托给服务,最终可能导致贫血领域模型.
工厂和存储库只是用于创建和持久化实体的帮助程序.大多数时候,他们只是在真正的问题域中没有类比,因此根据域逻辑,从工厂和存储库到服务和实体的引用是没有意义的.
关于您提供的示例,这是我实现它的方式
$article->addTag($tag);
$articleRepository->save($article);
Run Code Online (Sandbox Code Playgroud)
我不会直接访问底层集合,因为我可能希望在将它添加到集合之前Article执行一些域逻辑(强制约束,验证)Tag.
避免这样做
$articleService->addTag($article, $tag);
Run Code Online (Sandbox Code Playgroud)
仅使用服务从概念上执行不属于任何实体的操作.首先,尝试将其适合实体,确保它不适合任何实体.然后才使用服务.这样你就不会得到贫血的领域模型.
更新1
Eric Evans的"领域驱动设计:解决软件核心中的复杂性"一书引用:
应明智地使用服务,不允许剥夺其所有行为的ENTITIES和VALUE OBJECTS.
更新2
有人低估了这个答案,我不知道为什么.我只能怀疑原因.它可能与实体和服务之间的引用有关,也可能与示例代码有关.好吧,我对示例代码做不了多少,因为根据我自己的经验,这是我的观点.但是,我对参考部分进行了更多的研究,这就是我想出的.
我不是唯一一个认为引用实体服务,存储库和工厂不是一个好主意的人.我在SO中发现了类似的问题:
关于这个主题还有一些很好的文章,尤其是如果您迫切需要从您的实体调用名为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)
);
}
}
Run Code Online (Sandbox Code Playgroud)
因此,正如您所看到的,您没有对实体MailService内部的服务的引用Message,而是将其作为参数传递给实体的方法.本文作者" DDD:Services "在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);
}
}
Run Code Online (Sandbox Code Playgroud)
以下注释说明如下:
注:QueryService的,从数据库中获取行和创建对象的实用程序,简单解释的例子,但它并不 一定是一个真正的项目一个不错的设计.
如果你看一下我上面提到的DDDSample项目的源代码,你会发现除了包中的对象model,实体和值对象之外,实体没有任何引用.顺便说一句,DDDSample项目在"领域驱动设计:软件核心中处理复杂性"一书中有详细介绍......
此外,我想与你分享的另一件事是关于domaindrivendesign雅虎集团的类似讨论 .讨论中的这条消息引用了Eric Evans关于模型对象引用存储库的主题.
结论
总而言之,对实体的服务,存储库和工厂的引用并不好.这是最被接受的意见.即使存储库和工厂是域层的公民,它们也不是问题域的一部分.有时(例如在关于DDD的维基百科文章中)域服务的概念称为Pure Fabrication,这意味着类(服务)"不代表问题域中的概念".我更愿意将工厂和存储库称为纯制造,因为埃里克埃文斯在他的书中对服务的概念说了些什么:
但是当一个操作实际上是一个重要的领域概念时,一个服务组成了一个模型驱动设计的自然组成部分.在模型中声明为SERVICE,而不是作为实际上不代表任何东西的虚假对象,独立操作不会误导任何人.
根据上述说法,有时从您的实体调用服务可能是一个理想的事情.然后,您可以使用Double Dispatch方法,这样您就不必在实体类中保留对Service的引用.
当然,总有一些人不同意主流意见,如" 从实体访问域名服务"一文的作者.
实体是否应了解域中的工厂,存储库,服务?
实体绝对不应引用存储库或应用程序服务。如果实体使用工厂创建组成实体,则可以引用该工厂。如果实体将域服务用于某种行为,则它也可以依赖域服务。
存储库应该知道域中的服务吗?
一般没有。存储库仅应负责持久性。
现在,以防我想添加带有新标签的关系。有什么更好的方法呢?
这取决于您引用的图层。在典型的DDD体系结构中,您将同时拥有这两段代码。您将拥有一个文章应用程序服务,该服务封装域并提供一种精细的方法,例如在addTag何处传递文章ID和标签ID。此方法将检索适当的文章和标签实例(如果需要),然后:
$article->tags->add(TagEntity);
$articleRepository->save($article);
Run Code Online (Sandbox Code Playgroud)
取决于该域的所有外部层都将通过应用程序服务与其通信。
小智 1
我回答这个问题的前提是我认为没有正确的答案,只有不同的方法。
从领域对象的角度思考,我想说第一种方法是 DDD。您纯粹处理领域对象。
不过,我确实认为服务对象有用途,可以将其公开为 API/服务层的一部分。在这种情况下,您可以将#1 中的代码包装在服务#2 中。这使您可以避免将域对象暴露给外部使用者 - 并且您可以在更新域模型时保持外部接口/API 不变。
然而,这只是一种意见。
| 归档时间: |
|
| 查看次数: |
8503 次 |
| 最近记录: |