我现在正在尝试应用我对 DDD 的了解,但我对域模型中的依赖关系流有点困惑。
我的问题是:
另一件困扰我的事情是当我想向集合添加实体时如何处理集合。
假设我正在开发一个简单的 CMS。在 CMS 中,我有一个文章实体和标签集合,其中包含标签实体。
现在,如果我想添加与新标签的关系。什么是更好的方法呢? (PHP 中的示例)
$article->tags->add(TagEntity);
$articleRepository->save($article);
或者我可以通过服务来做到这一点。
$articleService->addTag($article, TagEntity);
你怎么认为?
谢谢。
最佳答案
实体和值对象不应该相互依赖。这些是所有 building blocks of DDD 中最基本的.它们代表您的问题域的概念,因此应该关注问题。通过使它们依赖于工厂、存储库和服务,您可以使焦点变得模糊。在实体和值对象中引用服务还有另一个问题。因为服务也拥有域逻辑你会倾向于将域模型的一些职责委托(delegate)给服务,这最终可能导致 Anemic Domain Model .
工厂和存储库只是用于创建和持久化实体的助手。大多数情况下,它们只是在实际问题域中没有类比,因此根据域逻辑,从工厂和存储库到服务和实体的引用是没有意义的。
关于您提供的示例,这就是我将如何实现它
$article->addTag($tag);
$articleRepository->save($article);
我不会直接访问底层集合,因为我可能想要
Article
在 Tag
上执行一些域逻辑(施加约束、验证)在将其添加到集合之前。避免这种情况
$articleService->addTag($article, $tag);
仅使用服务来执行概念上不属于任何实体的操作。首先,尝试让它适合一个实体,确保它不适合任何实体。然后才使用服务。这样你就不会得到贫血的领域模型。
更新 1
埃里克·埃文斯 (Eric Evans) 的“领域驱动设计:解决软件核心的复杂性”一书中的引述:
SERVICES should be used judiciously and not allowed to strip the ENTITIES and VALUE OBJECTS of all their behavior.
更新 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: 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);
}
}
下面的注释说明了以下内容:
Note: The QueryService, a utility for fetching rows from the database and creating objects, is simple for explaining examples, but it's not necessarily a good design for a real project.
如果您查看我上面提到的 DDDSample 项目的源代码,您会发现实体除了
model
中的对象之外没有任何对任何内容的引用。包,即实体和值对象。顺便说一下,DDDSample 项目在《领域驱动设计:解决软件核心的复杂性》一书中有详细的描述......另外,我想与您分享的另一件事是关于
domaindrivendesign Yahoo Group .此 message从讨论中引用了 Eric Evans 关于引用存储库的模型对象的主题。
结论
总而言之,从实体中引用服务、存储库和工厂是不好的。这是最被接受的意见。尽管存储库和工厂是域层的公民,但它们不是问题域的一部分。有时(例如在关于 DDD 的维基百科文章中)域服务的概念被称为 Pure Fabrication这意味着类(服务)“不代表问题域中的概念”。我更愿意将工厂和存储库称为纯制造,因为 Eric Evans 在他的关于服务概念的书中确实说了一些其他的话:
But when an operation is actually an important domain concept, a SERVICE forms a natural part of a MODEL-DRIVEN DESIGN. Declared in the model as a SERVICE, rather than as a phony object that doesn't actually represent anything, the standalone operation will not mislead anyone.
根据上述内容,有时从您的实体调用服务可能是明智之举。然后,您可以使用双调度方法,这样您就不必在您的实体类中保留对服务的引用。
当然,像Accessing Domain Services from Entities的作者一样,总有一些不同意主流观点的人。文章。
关于dependency-injection - 领域驱动设计和 IoC/依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14020196/