Ver*_*ern 5 domain-driven-design aggregate repository
我是众多试图理解聚合根的概念之一,我认为我已经掌握了它!但是,当我开始为这个示例项目建模时,我很快陷入了两难境地.
我有两个实体ProcessType和Process.一个Process不能存在没有ProcessType和ProcessType拥有众多Process上课.所以一个进程持有一个类型的引用,没有它就不能存在.
那应该ProcessType是聚合根?通过调用创建新进程processType.AddProcess(new Process()); 但是,我有其他实体只持有Process对它的引用,并通过它访问它的类型Process.Type.在这种情况下,ProcessType首先没有任何意义.
但聚合之外的AFAIK实体仅允许保留对聚合根的引用,而不允许对聚合内的实体进行引用.那么这里有两个聚合,每个聚合都有自己的存储库吗?
Dav*_*ers 15
我基本上同意西西弗所说的话,特别是关于不要将自己限制在DDD的"规则"中,这可能导致一个非常不合逻辑的解决方案.
就你的问题而言,我已多次遇到过这种情况,我将其称为"ProcessType"作为查找.查找是"定义"的对象,没有对其他实体的引用; 在DDD术语中,它们是值对象.我所说的查找的其他示例可以是团队成员的"RoleType",例如,可以是测试人员,开发人员,项目经理.甚至一个人的'标题'我会定义为查找 - 先生,小姐,夫人,博士
我会将您的流程聚合建模为:
public class Process
{
public ProcessType { get; }
}
Run Code Online (Sandbox Code Playgroud)
如您所说,这些类型的对象通常需要填充UI中的下拉列表,因此需要自己的数据访问机制.但是,我个人并没有为他们创建"存储库",而是"LookupService".这对我来说保留了DDD的优雅,通过严格保持"存储库"的聚合根源.
这是我的应用服务器上的命令处理程序的示例以及我如何实现它:
团队成员聚合:
public class TeamMember : Person
{
public Guid TeamMemberID
{
get { return _teamMemberID; }
}
public TeamMemberRoleType RoleType
{
get { return _roleType; }
}
public IEnumerable<AvailabilityPeriod> Availability
{
get { return _availability.AsReadOnly(); }
}
}
Run Code Online (Sandbox Code Playgroud)
命令处理程序:
public void CreateTeamMember(CreateTeamMemberCommand command)
{
TeamMemberRoleType role = _lookupService.GetLookupItem<TeamMemberRoleType>(command.RoleTypeID);
TeamMember member = TeamMemberFactory.CreateTeamMember(command.TeamMemberID,
role,
command.DateOfBirth,
command.FirstName,
command.Surname);
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
_teamMemberRepository.Save(member);
}
Run Code Online (Sandbox Code Playgroud)
客户端还可以使用LookupService来填充下拉列表等:
ILookup<TeamMemberRoleType> roles = _lookupService.GetLookup<TeamMemberRoleType>();
Run Code Online (Sandbox Code Playgroud)
Sis*_*hus 10
没那么简单.ProcessType最像是一个知识层对象 - 它定义了一个特定的过程.另一方面,进程是ProcessType的进程实例.您可能真的不需要或不想要双向关系.进程可能不是ProcessType的逻辑子进程.它们通常属于其他东西,如产品,工厂或序列.
同样根据定义,当您删除聚合根时,您将删除聚合的所有成员.当您删除进程时,我非常怀疑您确实要删除ProcessType.如果删除了ProcessType,您可能希望删除该类型的所有进程,但该关系已经不理想,并且只要您拥有ProcessType定义的历史进程,就不会删除定义对象.
我将从ProcessType中删除Processes集合,并找到一个更合适的父级(如果存在).我会将ProcessType保留为Process的成员,因为它可能定义了Process.操作层(Process)和知识层(ProcessType)对象很少作为单个聚合工作,因此我要么Process是聚合根,要么可能找到作为进程父级的聚合根.然后ProcessType将是一个外部类.Process.Type很可能是多余的,因为您已经拥有Process.ProcessType.摆脱它.
我有一个类似的医疗模式.有Procedure(操作层)和ProcedureType(知识层).ProcedureType是一个独立的类.Procedure是第三个对象Encounter的子代.Encounter是Procedure的聚合根.Procedure具有ProcedureType的引用,但它是一种方法.ProcedureType是一个定义对象,它不包含Procedures集合.
编辑(因为评论如此有限)
通过所有这些来记住一件事.许多人都是DDD纯粹主义者并坚持规则.但是,如果你仔细阅读埃文斯,他不断提出经常需要权衡的可能性.他还非常努力地描述逻辑和仔细考虑过的设计决策,而不是那些不了解目标或为了方便而绕过诸如聚合之类的事情的团队.
重要的是理解和应用概念而不是规则.我看到很多DDD将应用程序变成了不合逻辑和令人困惑的聚合等,而不是因为正在应用关于存储库或遍历的文字规则,这不是DDD的意图,但它通常是过度教条方法的产物采取.
那么这里的关键概念是什么:
通过将许多对象的行为减少到关键参与者的更高级别行为,聚合提供了使复杂系统更易于管理的手段.
聚合提供了一种方法,可确保在逻辑且始终有效的条件下创建对象,该条件还保留跨更新和删除的逻辑工作单元.
让我们考虑最后一点.在许多传统应用程序中,有人创建了一组未完全填充的对象,因为它们只需要更新或使用一些属性.下一个开发人员也来了,他也需要这些对象,有人已经在附近的某个地方创建了一个用于不同目的的地方.现在这个开发人员决定只使用这些,但他发现他们没有他需要的所有属性.所以他添加了另一个查询并填写了更多属性.最终因为团队不遵守OOP,因为他们采取的共同态度是OOP"对现实世界来说效率低且不切实际,并导致性能问题,例如创建完整对象来更新单个属性".他们最终得到的是一个充满嵌入式SQL代码和对象的应用程序,它们基本上可以在任何地方随机实现.更糟糕的是,这些对象是卑鄙无效的代理.一个过程似乎是一个过程,但它不是,它根据需要以任何给定点以不同方式部分填充.你最终得到了许多查询的球泥,不断地部分地填充对象,并且经常会出现很多无关的废话,比如空检查,这些废话不应该存在但是因为对象永远不是真正有效等所必需的.
聚合规则通过确保仅在某些逻辑点创建对象并始终使用一整套有效的关系和条件来防止这种情况.因此,既然我们完全理解了聚合规则的用途以及它们保护我们的内容,我们也希望了解我们也不想滥用这些规则并创建奇怪的聚合,这些聚合不能反映我们的应用程序的真正含义仅仅是因为这些汇总规则存在,必须始终遵循.
因此,当Evans说只为聚合创建存储库时,他说要在有效状态下创建聚合并保持这种方式,而不是直接绕过内部对象的聚合.您有一个Process作为根聚合,因此您可以创建一个存储库.ProcessType不是该聚合的一部分.你是做什么?好吧,如果一个对象本身并且它是一个实体,那么它是一个聚合体1.您可以为它创建一个存储库.
现在纯粹主义者会说你不应该拥有该存储库,因为ProcessType是一个值对象,而不是一个实体.因此,ProcessType根本不是聚合,因此您不会为它创建存储库.所以你会怎么做?您没有做的是将ProcessType作为某种人工模型的鞋拔,除了您需要获得它之外没有其它原因因此您需要一个存储库但是要拥有一个存储库,您必须将实体作为聚合根.你所做的就是仔细考虑这些概念.如果有人告诉你存储库是错误的,但是你知道你需要它以及它们可能会说它是什么,那么你的存储库系统是有效的并且保留了关键概念,你将存储库保持原样而不是扭曲你的模型来满足教条.
现在在这种情况下假设我对ProcessType是正确的,因为另一个评论员指出它实际上是一个值对象.你说它不能是一个价值对象.这可能有几个原因.也许你这样说是因为你使用NHibernate,但NHibernate模型在同一个表中实现与另一个对象的值对象不起作用.因此,您的ProcessType需要一个标识列和字段.通常由于数据库考虑因素,唯一实际的实现是在自己的表中使用带有id的值对象.或者您可以这么说,因为每个Process都通过引用指向单个ProcessType.
不要紧.由于概念,它是一个值Object.如果您有10个Process对象具有相同的ProcessType,则您有10个Process.ProcessType成员和值.无论每个Process.ProcessType指向单个引用,还是每个引用都有一个副本,它们仍应按定义完全相同,并且所有内容都可以与其他任何内容完全互换.这就是使它成为值Object的原因.那个说"它有一个Id因此不能成为你拥有一个实体的价值对象"的人正在犯一个教条错误.不要犯同样的错误,如果你需要一个ID字段给它一个,但不要说"它不能是一个值对象",实际上它是因为其他原因你必须给出一个ID至.
那么你怎么得到这个是对错?ProcessType是一个值对象,但由于某种原因,您需要它具有Id.Id本身并未违反规则.通过让10个进程都具有完全相同的ProcessType,您可以做到正确.也许每个都有一个本地深刻的副本,也许他们都指向一个对象.但是每个都是相同的,例如,ergo每个都有一个Id = 2.执行此操作时出错:10每个进程都有一个ProcessType,并且此ProcessType是完全可互换的,但现在每个进程都有自己的唯一ID.现在你有10个相同的实例,但它们只在Id中有所不同,并且总是只在Id中有所不同.现在你不再拥有一个Value Object了,不是因为你给它一个Id,而是因为你给它一个带有反映实体性质的实现的Id - 每个实例都是唯一的和不同的
合理?
| 归档时间: |
|
| 查看次数: |
3234 次 |
| 最近记录: |