Dependency injection 域驱动设计和IoC/依赖注入

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

我现在正在尝试应用我学到的关于DDD的知识,我对域模型中的依赖关系流有点困惑

我的问题是:

  • 实体是否应该了解域中的工厂、存储库和服务
  • 存储库是否应该知道域中的服务
  • 另一件困扰我的事情是,当我想向集合添加和实体时,如何处理集合

    假设我正在开发一个简单的CMS。在CMS中,我有一个包含标记实体的文章实体和标记集合

    现在,如果我想添加一个带有新标记的关系。有什么更好的方法?(PHP中的示例)

    或者我可以通过服务来完成

    $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);