Domain driven design 将服务交给实体是否正确?

Domain driven design 将服务交给实体是否正确?,domain-driven-design,Domain Driven Design,我知道,通常认为通过构造函数或setter将服务注入实体并让实体保留对它的引用是一种不好的做法 但是,在调用方法时,暂时将服务交给实体可以吗 例如,假设我想对实体的name字段进行版本设置,并让VersionService在每次调用setName时创建一个新版本,我可以执行以下操作: 公共类实体 { public void setNameString名称,VersionService服务 { this.name=名称; service.addVersionthis,名称; } } 在这段代码中,

我知道,通常认为通过构造函数或setter将服务注入实体并让实体保留对它的引用是一种不好的做法

但是,在调用方法时,暂时将服务交给实体可以吗

例如,假设我想对实体的name字段进行版本设置,并让VersionService在每次调用setName时创建一个新版本,我可以执行以下操作:

公共类实体 { public void setNameString名称,VersionService服务 { this.name=名称; service.addVersionthis,名称; } } 在这段代码中,我喜欢的是,如果不提供VersionService,就不能调用setName方法,从而强制执行所需的行为。通过模仿VersionService,它也很容易测试

我在by上的一篇文章中找到了这种方法的一个例子

但从对堆栈的一些讨论中,我认为普遍的共识是避免对域模型中的服务产生任何依赖


关于这个主题有什么想法吗?

您通常使用域事件生成新版本:

公共类实体 { public void setNameString名称 { this.name=名称; DomainEvent.Publishnew MyEntityGotRenameId; } } 公共类服务:iSubscribeeon { public void HandleMyEntityGotNamed domainEvent { var entity=repos.GetdomainEvent.Id; 附加版本性; } } 您订阅活动的方式取决于平台


请注意,setName并不表示域操作。应该是重命名或客户端定义的其他内容。

通常使用域事件生成新版本:

公共类实体 { public void setNameString名称 { this.name=名称; DomainEvent.Publishnew MyEntityGotRenameId; } } 公共类服务:iSubscribeeon { public void HandleMyEntityGotNamed domainEvent { var entity=repos.GetdomainEvent.Id; 附加版本性; } } 您订阅活动的方式取决于平台

请注意,setName并不表示域操作。应该是重命名或客户机定义的其他内容

但从一些关于堆栈的讨论来看,我认为 共识是避免对域中的服务有任何依赖 模型

就域服务而言,我很想知道反对在实体中使用它们的理由。域服务是包含在泛在语言中的一类域对象。在很多情况下,实体中的逻辑可以调用域服务中的逻辑,就像它调用另一个实体一样

本章第3段有一个很好的例子

但从一些关于堆栈的讨论来看,我认为 共识是避免对域中的服务有任何依赖 模型

就域服务而言,我很想知道反对在实体中使用它们的理由。域服务是包含在泛在语言中的一类域对象。在很多情况下,实体中的逻辑可以调用域服务中的逻辑,就像它调用另一个实体一样


中的第3段有一个很好的例子。

域事件方法很好,但它依赖于使用消息驱动的体系结构

不管怎样,特别是对于您给出的示例,我不会将该服务作为参数传递,因为在IMO中,使用该服务更新其他模型并不是实体关注的问题。可能是聚合根的实体只关心自己的模型。调用实体的代码也可以调用服务


但是,我认为可以将服务作为参数传递,以便检索其他一些必要的数据。遗憾的是,我现在想不出一个示例。但大多数情况下,您会将该服务作为构造函数依赖项传递,也就是说,您需要该服务完成的不仅仅是一个操作,而且您不能将数据作为方法参数提供

域事件方法很好,但它依赖于使用消息驱动的体系结构

不管怎样,特别是对于您给出的示例,我不会将该服务作为参数传递,因为在IMO中,使用该服务更新其他模型并不是实体关注的问题。可能是聚合根的实体只关心自己的模型。调用实体的代码也可以调用服务


但是,我认为可以将服务作为参数传递,以便检索其他一些必要的数据。遗憾的是,我现在想不出一个示例。但大多数情况下,您会将该服务作为构造函数依赖项传递,也就是说,您需要该服务完成的不仅仅是一个操作,而且您不能将数据作为方法参数提供

请详细说明你所说的Se是什么意思

服务。域服务?应用服务?基础设施服务?DDD中有各种层次的服务。很抱歉造成混淆。我在这里谈论的是域服务。请详细说明你所说的服务是什么意思。域服务?应用服务?基础设施服务?DDD中有各种层次的服务。很抱歉造成混淆。我在这里谈论的是域服务。确实是一种有趣的方法。您如何对域事件进行单元测试?您需要对域事件进行单元测试,以确保它们正确生成并且服务能够正确处理。不同的单元测试您不会直接测试setName是否触发对addVersion的调用,而是测试setName是否生成MyEntityGotRename事件,以及服务是否通过调用addVersion对MyEntityGotRename作出反应?是。我会这样做的。您甚至可以创建一个专用于此目的的类:class StoreVersionsFromMyEntityChanges:IsSubscribeen,IsSubscribeen确实很有趣。您如何对域事件进行单元测试?您需要对域事件进行单元测试,以确保它们正确生成并且服务能够正确处理。不同的单元测试您不会直接测试setName是否触发对addVersion的调用,而是测试setName是否生成MyEntityGotRename事件,以及服务是否通过调用addVersion对MyEntityGotRename作出反应?是。我会这样做的。您甚至可以创建一个专用于此目的的类:class StoreVersionsFromMyEntityChanges:IsSubscribeen,IsSubscribeen我相信这是完全正确的。域服务传递给实体或值对象是完全有效的。在体系结构上,应用程序服务将获得对适当域服务的引用,并将其传递给感兴趣的实体。域服务传递给实体或值对象是完全有效的。在体系结构上,应用程序服务将获得对适当域服务的引用,并将其传递给感兴趣的实体。我的问题中关于双重分派模式的链接是您提到的检索必要数据的方法的完美示例。我知道我的代码有点不同,但你的建议让我感到困扰的是,它没有强制执行一个事实,即如果不调用VersionService,就不能调用setName。如果我使用你的方法,就有可能以错误的方式使用我的域模型。所以你想确保setName和VersionService方法一起调用吗?我仍然不会把这种担心放在实体中,它们是两种不同的东西。可能使用域事件更干净,但这并不意味着这两件事会自动地出现在事务中。是的,这是我的应用程序中经常出现的问题:需要在两个不同的对象上同时调用两个方法。我不想单独公开setName,并引入单独调用它的风险。此外,我担心如果我的实体只公开getter和setter,那么它们会变得贫乏。我想,对于属于不同有界上下文的操作,您需要类似事务的行为。也许最好的方法是通过域事件,即消息驱动的体系结构,但您必须习惯最终一致性的想法。现在,如果您只需要在不同的对象上调用方法,但所有对象都是同一个有界上下文的一部分,那么您就不会有问题,因为聚合根可以实现该行为。您的意思是,聚合也应该包含版本,与其依赖服务来维护它们,我的问题中关于双重分派模式的链接是您提到的检索必要数据的方法的完美示例。我知道我的代码有点不同,但你的建议让我感到困扰的是,它没有强制执行一个事实,即如果不调用VersionService,就不能调用setName。如果我使用你的方法,就有可能以错误的方式使用我的域模型。所以你想确保setName和VersionService方法一起调用吗?我仍然不会把这种担心放在实体中,它们是两种不同的东西。可能使用域事件更干净,但这并不意味着这两件事会自动地出现在事务中。是的,这是我的应用程序中经常出现的问题:需要在两个不同的对象上同时调用两个方法。我不想单独公开setName,并引入单独调用它的风险。此外,我担心如果我的实体只公开getter和setter,那么它们会变得贫乏。我想,对于属于不同有界上下文的操作,您需要类似事务的行为。也许最好的方法是通过域事件,即消息驱动的体系结构,但您必须习惯最终一致性的想法。现在,如果您只需要在不同的对象上调用方法,但所有方法都是同一个有界上下文的一部分,那么您就没有问题了,因为聚合根可以实现这种行为,您是说agg regate也应该包含这些版本,而不是依赖服务来维护它们?