7

1)当Domain layer使用Infrastructure Service IS时,它的接口定义在Domain layer,而它的实现定义在Infrastructure layer

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

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

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

 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注入域实体的主要原因是因为它会违反持久性无知规则?

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

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

2)

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

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

第二次更新:

1)

一种)

这是原因之一。另一个是创建不必要的耦合。如果实体上的单个行为只需要一个存储库,为什么总是将它注入实体?另外,现在您必须考虑如何解决这些依赖关系?使实体成为依赖注入图的一部分?这很快就会使实体的职责超载。

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

b)

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

我-但是在您之前的几篇文章中,您注意到可以将IS作为参数传递给Domain Entity的方法,但是在这里您说如果此域方法在IS实例上仅使用一种方法,它将违反 SRP?

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

d)

PI 是通过使用接口来维护的。

我读过几次,即使域方法通过接口引用存储库,它仍然被认为是违反 PI。你为什么不同意呢?

2)

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

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

b)

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

我假设您建议使用基于角色的界面?但是,甚至不会将基于角色的接口(由说Domain Service实现)注入到Domain Entity中会导致违反 SRP,因为正如您在1a中指出的那样,注入的功能很可能只需要Domain Entity的单一行为? !

在将IS传递给域实体的方法方面,您和 Aaron Hawkins 似乎是对立的?!

第三次更新:

1)

一种)

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

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

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();
}

IV – b)GetBreedGetPedigree行为FavSnack方法?如果是,那么 propertiesBreedPedigreeSnack应该被视为行为,因为它们本质上提供了相同的功能(假设GetBreed,GetPedigree并且FavSnack不做一些繁重的计算,而只是简单地返回对象):

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

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

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

IV – d)

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

但是,如果域实体的主要职责是管理其生命周期,那么不包括与管理生命周期无关的行为就违反了 SRP(在上面的示例中,诸如此类的方法Dog.DoBark很可能与Dog' s 生命周期)?!

d)

一世。

将 IS 传递给 DE 行为方法更好,但是如果 IS 接口有很多与手头行为无关的东西,则可能违反 SRP/ISP。这是 ISP 的基本前提——依赖关系应该建立在特定的接口上,而不是恰好包含所需功能的臃肿接口。

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

二、– 我明白你在说什么,但我的困惑源于这样一个事实,根据以下对 ISP 的定义,该术语应仅用于指定实现特定接口的对象ServObj违反 ISP,而ServObj中的对象注入违反 SRP(由于接收ServObj):

接口隔离原则与单一职责原则相似,都处理职责的凝聚力。实际上,ISP可以理解为将SRP应用于对象的公共接口。

在一定程度上,ISP可以被认为是单一责任原则的一个子集,或更具体的形式。然而,ISP 的视角转变检查了给定类或模块的公共 API。

谢谢你

4

2 回答 2

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) To be sure, when I say injecting IS I mean having a required instance reference to an IS from a DE. This is discouraged for reasons above. Passing IS to a DE behavioral method is better, however can violate SRP/ISP if IS interface has lots of things unrelated to the behavior at hand. This is the basic premise of ISP - dependencies should be made on specific interfaces as opposed to bloated interfaces that happen to contain the required functionality.

于 2013-01-30T21:04:17.780 回答
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 - 你的意思是只有应用层应该创建和使用域服务,而域实体甚至不应该知道它们的存在?但为什么?

以下是我想到的一些。我不会认为这是一个完整的列表,但应该足以证明我的陈述。

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

  • 存储库/域服务本质上是基础设施,因此不应将其组合到您的业务域中,这会使您的业务逻辑保持简洁和富有表现力。(这些接口在域中为实现者提供了一种实现存储库/域服务的合同方式,从而防止应用程序引用比需要更多的项目。)

  • 使您的域对存储库/域服务一无所知,这会进一步强制解耦和可能的依赖关系,这将朝着错误的方向发展(基础设施项目应该引用域项目,反之亦然,如果有人通过传入一个没有依赖注入的情况下实现您的域,则可能发生这种情况参考具体实现)。

  • 变更管理/可维护性(参见洋葱架构

B - 我假设您发布的所有代码都驻留在应用程序层中?

你是对的好先生!

于 2013-01-30T21:01:39.617 回答