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)GetBreed
,GetPedigree
和FavSnack
行为方法是?如果是,则属性Breed
,Pedigree
和Snack
也应被视为行为,因为它们基本上提供相同的功能(假设GetBreed
,GetPedigree
并且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。
谢谢
答案 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层中?
你是正确的先生!