域服务如何调用基础架构服务?

use*_*291 7 domain-driven-design

1)当Domain层使用Infrastructure Service IS时,其接口在Domain层中定义,而其实现在Infrastructure层中定义.

我们不应将IS(例如存储库或电子邮件服务)直接注入域实体:

class Foo
{
       IRepository repo;
           ...
       public int DoSomething()
       {
            var info = repo.Get...;
              ...
       }
}
Run Code Online (Sandbox Code Playgroud)

相反,如果域实体的某个方法需要特定的IS,那么Application层可以将该IS作为该方法的参数传递:

 class Foo
{
           ...
       public int DoSomething(IRepository repo)
       {
            var info = repo.Get...;
              ...
       }
}
Run Code Online (Sandbox Code Playgroud)

a)我假设IS也不应该直接注入域服务:

class TransferService
{
         IRepository repo;
             ...
         public bool Transfer()
         {
            var info = repo.Get...;
              ...
         } 
} 
Run Code Online (Sandbox Code Playgroud)

,但IS应该作为参数传递给那些打算使用它的域服务方法:

class TransferService
{
         public bool Transfer(IRepository repo)
         {
            var info = repo.Get...;
              ...
         } 
} 
Run Code Online (Sandbox Code Playgroud)

b)我假设如果域实体的方法需要使用域服务,它不需要通过参数接收它(尽管将域服务作为参数传递具有明确传达方法的依赖性的好处),但是可以调用它是直接的,因为域实体域服务都是域概念:

class Foo
{
       ...

       public int DoSomething()
       {
            var info = TransferService.Transfer(...);
              ...
       }
}
Run Code Online (Sandbox Code Playgroud)

更新:

1)

如果IS需要它来实现功能,则可以将其注入域服务 - 即不传递给方法.这与实体不同,这是因为域服务是无状态的,因此可以配置一次所需的依赖项并在需要时使用,例如由其他实体使用.

a)IS不应该注入域实体的主要原因是它们的状态性质?

b)但我认为不将IS注入域实体的主要原因是因为它违反了持久性无知规则?

c)如果我们用IS注入实体的状态性质会导致哪些问题?

d)不将IS注入域服务违反PI?如果没有,为什么不呢?

2)

域服务应作为参数传递给实体,就像将存储库接口作为参数传递一样.同样的原则适用.

但是,与域服务是一个域的概念,所以你是什么"原则同样适用"是什么意思?也就是说,将域服务注入域实体不会违反PI,而注入存储库则会!

第二次更新:

1)

一个)

这是一个原因.另一个是创造不必要的耦合.如果实体上只有一个行为需要存储库,为什么要一直将它注入实体?此外,现在您必须考虑如何解决这些依赖关系?使实体成为依赖注入图的一部分?这很快就超出了实体的责任.

因此,如果我理解正确 - 将IS注入域服务 DS不会违反SRP,因为DS正在使用它来执行其指定的任务(即其指定的责任),而将IS注入域实体则违反了SRP,因为主要责任中域实体的重点是它的生命周期和身份,以及IS大多数时候是不是在管理这两个任务(即侧重于生命周期和身份)的一个组成部分?

b)

您仍然可以将IS传递给域实体方法,这里的问题不会违反PI,因为您传递的是接口,而不是实现.如果域方法仅在IS接口上使用一种方法,则问题是违反SRP.

我 - 但是在你以前的几篇文章中你注意到可以将IS作为参数传递给域实体的方法,但是在这里你说如果这个域方法在IS实例上只使用一种方法它会违反SRP 吗?

II - 如果IS实现了一个包含单个方法的基于角色的接口,而是我们将这个基于角色的接口作为域方法的参数传递,您是否仍然认为这违反了SRP?如果没有,为什么不呢?

d)

使用接口维护PI.

我已多次阅读过,即使domain方法通过接口引用存储库,它仍被视为违反了PI.你为什么不同意这个?

2)

然而,最好是非常明确.因此,不是传递存储库并隐式理解它恰好为实体提供服务,而是将提供的功能声明为其自己的接口,并让实体依赖于该实体.

a)没有将域服务注入域实体的唯一原因是违反了SRP?

b)

将提供的功能声明为其自己的接口

我假设您建议使用基于角色的界面?但是,甚至不会将基于角色的接口(由域服务实施)注入到域实体中导致违反SRP,因为正如您在1a中所述,注入的功能很可能只需要域实体的单个行为? !

看来你和Aaron Hawkins在将IS传递给领域实体的方法方面是相反的吗?

第三次更新:

1)

一个)

因此,如果我理解正确 - 将IS注入域服务DS不会违反SRP,因为DS正在使用它来执行其指定的任务(即其指定的责任),而将IS注入域实体则违反了SRP,因为主要责任领域实体正在关注其生命周期和身份,而且几乎所有时代都不是管理这两项任务(即关注生命周期和身份)的重要组成部分?

是的,这是正确的,也是主要原因之一.

I - 从远处看,通过将IS注入域实体 DE似乎非常合理,这个DE会违反SRP,因为IS不会有助于管理指定给DE的两个任务.

但是在尝试更详细地想象这种情况时,这有点困难.也就是说,如果DE的方法专注于管理两个指定任务(即其生命周期和身份),那么如果这些方法中的一个需要IS,那么假设它需要IS来完成两个指定的任务并且是不合理的不是与DE的生命周期和身份无关的其他任务吗?如果是,那么我们怎么能声称DE违反了SRP?

II - 我也很难想象管理DE的生命周期和身份究竟意味着什么.对于初学者,一旦为DE分配了身份,该身份就不会改变.那么我们需要管理它的身份呢?

III - 管理DE的生命周期是什么意思?也许在DE上定义不变量来保持数据的一致性或......?

IV - 那么现实世界实体所执行的所有其他任务(即那些与DE的生命周期和身份无关的任务)应该从DE中分解并放入相关对象中?

d)

如果IS实现了一个包含单个方法的基于角色的接口,而我们将这个基于角色的接口作为域方法的参数传递,您是否仍然认为这违反了SRP?如果没有,为什么不呢?

这样做并不可怕,但它有可能违反SRP或guillaume31-ISP更明确规定.

我不确定我们如何声称注入IS进入DE可能会违反ISP,因为据我所知,ISP只能被实现此接口的对象所违反,而不是注入此接口实现的对象?

第四次更新:

我开始意识到SRP比我最初的想法更令人困惑

一个)

与实体相关联的行为(通常需要进行状态更改)也应放入实体中.如果此类行为需要使用服务,请传递该服务,但通常会尽可能多地将行为放入实体中.

IV - 1以下行为方法不包括状态更改,但我认为它们也属于Dog实体:

class Dog
{
            ...
       void DoBark();
       void DoFetch();
       void DoGuard();

       Breed    GetBreed();
       Pedigree GetPedigree();
       Snack    FavSnack();
}
Run Code Online (Sandbox Code Playgroud)

IV - b)是否GetBreed,GetPedigree以及FavSnack行为方式?如果是,那么属性Breed,PedigreeSnack应该被视为行为,因为它们本质上提供相同的功能(假设GetBreed,GetPedigree并且FavSnack不做一些繁重的计算,而只是返回对象):

class Dog
{
            ...
       void DoBark();
       void DoFetch();
       void DoGuard();

       Breed    Breed { get{...} }
       Pedigree Pedigree { get{...} }
       Snack    Snack { get{...} }
}
Run Code Online (Sandbox Code Playgroud)

IV - c)如果上述属性也有固定,我们会说它们包含状态改变行为吗?

IV - d)

与实体相关联的行为(通常需要进行状态更改)也应放入实体中.

但是,如果Domain Entity的主要职责是管理其生命周期,那么不包括与管理生命周期无关的行为就会违反SRP(在上面的例子中,Dog.DoBark很可能与方法无关Dog的方法)生命周期)?!

d)

一世.

将IS传递给DE行为方法更好,但是如果IS接口有很多与手头行为无关的东西,则会违反SRP/ISP.这是ISP的基本前提 - 应该在特定接口上进行依赖,而不是碰巧包含所需功能的膨胀接口.

因此,如果IS作为参数传递给DE的一个行为方法确实有一些与手头行为无关的操作,但是DE的方法M不使用任何与M应该处理的行为无关的IS方法,我们仍然认为DE为违反SRP/ISP?

II.- 我理解你在说什么,但我的困惑是因为根据ISP的以下定义,该术语只应用于指定实现特定接口的对象ServObj违反ISP,而ServObj中的对象注入是违反SRP(由于接收ServObj):

接口隔离原则类似于单一责任原则,因为它们都涉及责任的凝聚力.实际上,ISP可以理解为SRP应用于对象的公共接口.

在某种程度上,ISP可以被视为单一责任原则的子集或更具体的形式.但是,ISP的透视转移检查给定类或模块的公共API.

谢谢

eul*_*rfx 6

1a)如果IS需要它来实现功能,则可以将其注入域服务 - 即不传递给方法.这与实体不同,这是因为域服务是无状态的,因此可以配置一次所需的依赖项并在需要时使用,例如由其他实体使用.

1b)域服务应作为参数传递给实体,就像将存储库接口作为参数传递一样.同样的原则适用.此外,传递整个存储库接口可能会创建不必要的耦合,因此最好声明并传递特定于角色的接口.

UPDATE

1a)这是一个原因.另一个是创造不必要的耦合.如果实体上只有一个行为需要存储库,为什么要一直将它注入实体?此外,现在您必须考虑如何解决这些依赖关系?使实体成为依赖注入图的一部分?这很快就超出了实体的责任.

1b)违反PI是违反单一责任原则的更一般概念的一个例子.您仍然可以将IS传递给域实体方法,这里的问题不会违反PI,因为您传递的是接口,而不是实现.如果域方法仅在IS接口上使用一种方法,则问题是违反SRP.

1c)见1a)

1d)否,因为使用接口维护PI.

2)是的,这就是我建议避免直接传递存储库接口的原因.原则是相似的,因为您可以将存储库接口和域服务都视为抽象服务,它们为域实体提供一些有趣的行为.然而,最好是非常明确.因此,不是传递存储库并隐式理解它恰好为实体提供服务,而是将提供的功能声明为其自己的接口,并让实体依赖于该实体.

更新2

1a)是的,这是正确的,也是主要原因之一.

1b)这样做并不可怕,但它有可能违反SRP或guillaume31 - ISP更明确规定.所以它比注入实体实例更好,但可以通过声明一个特定的接口来改进.

不,如果您创建基于角色的界面,那么这就像在这种情况下一样好.

1d)如果实体使用存储库接口来保持自身,那么这违反了PI.但是,如果它使用存储库接口来执行某些查询以运行其业务逻辑,那么我们实际上并没有谈论持久性.要真正解耦存储库,请使用特定于角色的界面.

2a)是的,出于同样的原因,将存储库注入实体是不合适的.

2b)是的,但我只是建议在您将域服务传递给实体的行为方法的场景中,而不是将其注入实体实例本身.

更新3

1a)这可能是我同意的争论点.但是,从实际的角度来看,最好设计DE,使其对外部服务的依赖性被消除或者非常明确和孤立.如果您的DE对IS具有实例依赖性,则意味着无论何时创建DE的实例(例如在重构期间或创建新实体时),都必须在此时提供IS.这使依赖图变得复杂.此外,如果DE负责维护其自身状态的完整性,为什么会依赖外部服务?可能需要服务来调用某些行为,但是通常可以在没有外部服务的情况下完成维护完整性.实际上,以这种方式对外部服务的依赖通常是一种混合责任的气味.

II,III)这包括保护不变量等内容.例如,实体可以通过引发异常来确保其值不会进入不一致状态.这是OOP的基本前提 - 封装状态和暴露行为.

IV)与实体相关的行为(通常需要进行状态变更)也应该放入实体中.如果此类行为需要使用服务,请传递该服务,但通常会尽可能多地将行为放入实体中.这并不总是完美的,但你可以追求理想.

d)当然,当我说注入IS时,我的意思是从DE中获得对IS的必需实例引用.出于上述原因,不鼓励这样做.将IS传递给DE行为方法更好,但是如果IS接口有很多与手头行为无关的东西,则会违反SRP/ISP.这是ISP的基本前提 - 应该在特定接口上进行依赖,而不是碰巧包含所需功能的膨胀接口.