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

时间:2013-01-30 20:04:36

标签: domain-driven-design

1)当域层使用基础架构服务 IS 时,其界面在域层中定义,而其实现是在 Infrastructure层中定义的。

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

class Foo
{
       IRepository repo;
           ...
       public int DoSomething()
       {
            var info = repo.Get...;
              ...
       }
}

相反,如果域实体的某种方法需要特定的 IS ,则应用层可以传递 IS < / strong>作为该方法的参数:

 class Foo
{
           ...
       public int DoSomething(IRepository repo)
       {
            var info = repo.Get...;
              ...
       }
}

a)我认为 IS 也不应直接注入域名服务

class TransferService
{
         IRepository repo;
             ...
         public bool Transfer()
         {
            var info = repo.Get...;
              ...
         } 
} 

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

class TransferService
{
         public bool Transfer(IRepository repo)
         {
            var info = repo.Get...;
              ...
         } 
} 

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

class Foo
{
       ...

       public int DoSomething()
       {
            var info = TransferService.Transfer(...);
              ...
       }
}

更新

1)

  

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

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

b)但我认为不将 IS 注入域实体的主要原因是因为它会违反 Persistence Ignorance 规则?

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

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

2)

  

域服务应该像您一样作为参数传递给实体   将存储库接口作为参数传递。同样的原则   应用

但与存储库不同,域名服务是一个域名概念,那么你的意思是什么?同样的原则适用于&#34;?即,将域服务注入域实体并不会违反 PI ,同时注入存储库会! / p>

第二次更新:

1)

A)

  

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

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

b)中

  

您仍然可以将IS传递给域实体方法和问题   因为您通过了界面,所以不会违反PI,   不是实施。问题是如果违反了SRP   domain方法只在IS接口上使用了一种方法。

我 - 但在您以前的几篇帖子中,您注意到可以将 IS 作为参数传递给域实体的方法,但是这里如果此域名方法仅在 IS 实例上使用了一种方法,那么它会违反SRP吗?

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

d)

  

使用接口维护PI。

我已多次阅读,即使域方法通过接口引用存储库,它仍被视为违反PI。为什么不同意这一点?

2)

  

然而,最好是非常明确。所以不要过去   存储库并隐含地理解它碰巧提供了一个   对实体的服务,将提供的功能声明为自己的功能   接口并让实体依赖于它。

a)未将域服务注入域实体的单一原因是由于违反了SRP?

b)

  

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

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

看来你和Aaron Hawkins在将 IS 传递给域实体的方法方面是对立的?!

第三次更新:

1)

A)

  
    

所以,如果我理解正确的话 - 将IS注入域服务DS     不会违反SRP,因为DS正在使用它来执行其指定的SRP     任务(即其指定的责任),同时注入IS     域实体是违反SRP的主要责任     Domain Entity专注于其生命周期和身份,以及ISmost     时代不是管理这两项任务的重要组成部分(即     关注生命周期和身份)?

  
     

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

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

但是在尝试更详细地想象这个场景时会有点困难。也就是说,如果 DE 的方法专注于管理两个指定任务(即其生命周期和身份),那么如果这些方法中的一个需要 IS ,那么就不会这样做。假设它需要 IS 来完成两个指定的任务而不是与 DE&#39> 生命周期和身份无关的其他任务?如果是,那么我们如何声称 DE 违反了SRP?

II - 我也很难想象通过管理 DE&#39> 生命周期和身份确切意味着什么。对于初学者,一旦为 DE 分配了身份,此身份就不会发生变化。那么我们需要管理它的身份呢?

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

IV - 因此,所有其他任务(即那些与DE的生命周期和身份无关的任务)应该从 DE 中排除并放入进入相关对象?

d)

  
    

如果IS实现了包含单个方法的基于角色的界面,     而是我们将这个基于角色的接口作为域的参数传递     方法,你还会认为这违反了SRP吗?如果不,     为什么不呢?

  
     

这样做并不可怕,但它有可能违反SRP或   由guillaume31-ISP更明确地指定。

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

第四次更新:

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

A)

  

与实体相关的行为,通常需要国家   变化,也应该放入实体。如果这样的行为   需要使用服务,传递该服务,但通常尝试   尽可能多地将行为放入实体中。

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

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

       Breed    GetBreed();
       Pedigree GetPedigree();
       Snack    FavSnack();
}

IV - b)GetBreedGetPedigreeFavSnack行为方法是?如果是,则属性BreedPedigreeSnack也应被视为行为,因为它们基本上提供相同的功能(假设GetBreedGetPedigree并且FavSnack不进行繁重的计算,只需返回对象):

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

       Breed    Breed { get{...} }
       Pedigree Pedigree { get{...} }
       Snack    Snack { get{...} }
}

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

IV - d)

  

与实体相关的行为,通常需要国家   变化,也应该放入实体。

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

d)

予。

  

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

所以,如果 IS 作为参数传递给DE的一个行为方法确实有一些与手头行为无关的操作,但DE&#39; s方法 M 没有使用任何 IS 与行为无关的方法 M 应该处理,我们仍然会考虑DE违反SRP / ISP?

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

  

接口隔离原理类似于Single   责任原则在于既处理了凝聚力   责任。实际上,ISP可以理解为   将SRP应用于对象的公共接口。

     

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

谢谢

2 个答案:

答案 0 :(得分:6)

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

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

<强>更新

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的基本前提 - 应该在特定接口上进行依赖,而不是碰巧包含所需功能的膨胀接口。

答案 1 :(得分:3)

域实体应该对持久性(存储库)或进程(域服务)一无所知。相反,您的应用程序将协同编排这些内容。

以下是存储库业务流程:

ISomeAggregateRepository repository = //TODO: Construct repository and load it with the de-hydrated aggregates you intend to work with under your scope.
foreach (SomeAggregate someAggregate in repository) //The repository functions like an in memory collection.
{
    someAggregate.DoSomethingCool(); //The domain aggregate would expose an event to let the repository know that this method has been called and that the instance should be saved afterwards. (mimicking saving to a reference)
}

以下是域服务编排:

ISomeDomainService service = new ConcreteInfrastructuralSpecificDomainService();
service.DoSomethingCool(SomeAggregate target); //Send the aggregate to the domain service, not the other way around.

我和你在同一条轨道上的时间最长,并且非常困惑,但是一旦我弄清楚如何按照我的例子描述的方式组织代码,事情变得更加清晰。

请记住,应用程序应选择您的存储库,域服务以及可能的工作单元的实现(通常是特定于基础架构)。

希望这有帮助!

<强>更新

  

A - 您的意思是只有应用层才能创建和使用域服务,而域实体甚至不知道它们存在?但为什么呢?

以下是我想到的一些问题。我不认为这是一个完整的清单,但应该足以保证我的陈述。

  • 封装(域聚合的内部工作方式不会暴露给将要传递的存储库和/或域服务的实现)

  • 存储库/域服务本质上是基础架构,因此不应将其合并到您的业务域中,从而保持业务逻辑的清洁和富有表现力。 (接口位于域中,为实现者提供实现存储库/域服务的合同方式,从而防止应用程序引用比需要更多项目的需要。)

  • 让您的域名对存储库/域名服务一无所知,进一步强制解耦和可能的依赖性,这将导致错误的方向(基础设施项目应该引用域项目,反之亦然,如果有人实现您的域,可能会发生这种情况通过传入引用的具体实现来实现依赖注入。

  • 变更管理/可维护性(请参阅Onion Architecture

  

B - 我假设你发布的所有代码都在Application层中?

你是正确的先生!