dependency-injection - 领域驱动设计和 IoC/依赖注入(inject)

标签 dependency-injection dependencies domain-driven-design inversion-of-control

我现在正在尝试应用我对 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);
    

    我不会直接访问底层集合,因为我可能想要 ArticleTag 上执行一些域逻辑(施加约束、验证)在将其添加到集合之前。

    避免这种情况
    $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 中发现了类似的问题:
  • Is it ok for entities to access repositories?
  • DDD - Dependecies between domain model, services and repositories
  • DDD - the rule that Entities can't access Repositories directly
  • DDD Entities making use of Services

  • 也有一些关于这个主题的好文章,尤其是这篇 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/

    相关文章:

    java - 阴影 jar 中的 Maven 依赖排除

    java - 无法从外部库导入 MediaPlayer

    domain-driven-design - DDD 领域实体与持久化实体

    c# - ASP.NET N-Layered/DDD 架构和 Window 服务软件架构之间有区别吗?

    java - 用于 Quartz 作业的 HK2 工厂,执行后不会破坏服务

    typescript - Angular 7 上的 NullInjectorError : No provider for e!

    java - 使用 Spring 引导 play 2.4 Java 应用程序的方法是什么?

    spring - 依赖查找 &Ioc 之间有什么关系

    python-2.7 - python 依赖

    java - DDD 中聚合通信的正确方法