我需要帮助来找到我的聚合根和边界。
我有 3 个实体:Plan、PlannedRole 和 PlannedTraining。每个计划可以包括许多 PlannedRoles 和 PlannedTrainings。
解决方案 1:起初我认为 Plan 是聚合根,因为 PlannedRole 和 PlannedTraining 在计划的上下文之外没有意义。他们总是在一个计划之内。此外,我们有一条业务规则,规定每个计划最多可以有 3 个 PlannedRoles 和 5 个 PlannedTrainings。所以我想通过将计划指定为聚合根,我可以强制执行这个不变量。
但是,我们有一个搜索页面,用户可以在其中搜索计划。结果显示了计划本身的一些属性(并且没有任何 PlannedRoles 或 PlannedTrainings)。我想如果我必须加载整个聚合,它会产生很多开销。有近 3000 个计划,每个计划可能有几个 child 。将所有这些对象一起加载,然后在搜索页面中忽略 PlannedRoles 和 PlannedTrainings 对我来说没有意义。
解决方案 2:我刚刚意识到用户还需要 2 个搜索页面,他们可以在其中搜索计划角色或计划培训。这让我意识到他们正在尝试独立访问这些对象并且“脱离”Plan 的上下文。所以我认为我最初的设计是错误的,这就是我想出这个解决方案的原因。所以,我认为这里有 3 个聚合,每个实体 1 个。
这种方法使我能够独立搜索每个实体,还解决了解决方案 1 中的性能问题。但是,使用这种方法我无法强制执行我之前提到的不变量。
还有另一个不变量表明计划只有在它具有特定状态时才能更改。因此,我应该无法将任何 PlannedRoles 或 PlannedTrainings 添加到不在该状态的计划中。同样,我无法使用第二种方法强制执行此不变量。
任何建议将不胜感激。
干杯,
莫什
最佳答案
在设计我的模型时,我遇到了类似的问题,并提出了这个我认为可能对你有帮助的问题,尤其是关于你的第一点。
DDD - How to implement high-performing repositories for searching .
在搜索方面,我不使用“模型”,而是使用专门的搜索存储库返回“摘要”对象......即“PlanSummary”。这些只不过是信息对象(可以被认为更像是报告)并且没有在事务意义上使用——我什至没有在我的模型类库中定义它们。通过创建这些专用存储库和类型,我可以实现高性能搜索查询,这些查询可以包含分组数据(例如 PlannedTraining 计数),而无需将聚合的所有关联加载到内存中。一旦用户在 UI 中选择了这些摘要对象之一,我就可以使用 ID 获取实际的模型对象并执行事务操作并提交更改。
因此,对于您的情况,我将为所有三个实体提供这些专门的搜索存储库,并且当用户希望对一个实体执行和操作时,您始终获取它所属的计划聚合。
通过这种方式,您可以获得高性能搜索,同时仍然使用所需的不变量来维护您的单个聚合。
编辑 - 示例:
好的,所以我想实现是主观的,但这就是我在应用程序中处理它的方式,以“TeamMember”聚合为例。用 C# 编写的示例。我有两个类库:
模型库包含聚合类,强制执行所有不变量,报告库包含这个简单的类:
public class TeamMemberSummary
{
public string FirstName { get; set; }
public string Surname { get; set; }
public DateTime DateOfBirth { get; set; }
public bool IsAvailable { get; set; }
public string MainProductExpertise { get; set; }
public int ExperienceRating { get; set; }
}
报告库还包含以下接口(interface):
public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary>
{
}
这是应用程序层(在我的例子中恰好是 WCF 服务)将使用的接口(interface),并将通过我的 IoC 容器(Unity)解析实现。 IReportRepository 与基础 ReportRepositoryBase 一样位于 Infrastructure.Interface 库中。所以我的系统中有两种不同类型的存储库 - 聚合存储库和报告存储库......
然后在另一个库 Repositories.Sql 中,我有实现:
public class TeamMemberSummaryRepository : ITeamMemberSummaryRepository
{
public IList<TeamMemberSummary> FindAll<TCriteria>(TCriteria criteria) where TCriteria : ICriteria
{
//Write SQL code here
return new List<TeamMemberSummary>();
}
public void Initialise()
{
}
}
那么,在我的应用层:
public IList<TeamMemberSummary> FindTeamMembers(TeamMemberCriteria criteria)
{
ITeamMemberSummaryRepository repository
= RepositoryFactory.GetRepository<ITeamMemberSummaryRepository>();
return repository.FindAll(criteria);
}
然后在客户端中,用户可以选择其中一个对象,并在应用层对其中一个对象执行操作,例如:
public void ChangeTeamMembersExperienceRating(Guid teamMemberID, int newExperienceRating)
{
ITeamMemberRepository repository
= RepositoryFactory.GetRepository<ITeamMemberRepository>();
using(IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
{
TeamMember teamMember = repository.GetByID(teamMemberID);
teamMember.ChangeExperienceRating(newExperienceRating);
repository.Save(teamMember);
}
}
关于domain-driven-design - DDD : Aggregate Roots,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2558469/