我需要帮助来找到我的聚合根和边界。
我有3个实体: Plan、PlannedRole和PlannedTraining。每个计划可以包含多个PlannedRoles和PlannedTrainings。
解决方案1:一开始,我认为计划是聚合的根,因为PlannedRole和PlannedTraining在计划的上下文之外没有意义。他们总是在一个计划之内。此外,我们有一个业务规则,规定每个计划最多可以有3个PlannedRoles和5个PlannedTrainings。因此,我认为通过将Plan指定为聚合根,我可以强制执行此不变量。
但是,我们有一个搜索页面,用户可以在其中搜索计划。结果显示了计划本身的一些属性(而不是其PlannedRoles或PlannedTrainings)。我想如果我必须加载整个聚合,它将有很多开销。有近3000个计划,每个计划都可能有几个孩子。将所有这些对象加载在一起,然后在搜索页面中忽略PlannedRoles和PlannedTrainings,对我来说是没有意义的。
解决方案2:我刚刚意识到用户想要另外两个搜索页面,在那里他们可以搜索计划的角色或计划的培训。这让我意识到他们试图独立地访问这些对象,并且“脱离”了Plan的上下文。因此,我认为我的初始设计是错误的,这就是我提出这个解决方案的原因。所以,我想在这里有3个聚合,每个实体1个。
这种方法使我能够独立地搜索每个实体,还解决了解决方案1中的性能问题。但是,使用这种方法,我不能强制执行我前面提到的不变量。
还有另一个不变量,它规定只有当计划处于特定状态时才能更改它。因此,我不能向未处于该状态的计划添加任何PlannedRoles或PlannedTrainings。同样,我不能在第二种方法中强制使用这个不变量。
任何建议都将不胜感激。
干杯,莫什
发布于 2010-04-03 04:23:53
我在设计我的模型时遇到了类似的问题,我问了这个问题,我认为这可能会对你有所帮助,特别是关于你的第一点。
DDD - How to implement high-performing repositories for searching。
当涉及到搜索时,我不使用'model',而是使用专门的搜索存储库来返回'Summary‘对象……即“‘PlanSummary”。这些只不过是信息对象(可以认为更像是报告),并不用于事务意义上-我甚至没有在我的模型类库中定义它们。通过创建这些专用存储库和类型,我可以实现高性能的搜索查询,这些查询可以包含分组数据(例如PlannedTraining计数),而无需在内存中加载聚合的所有关联。一旦用户在UI中选择了这些摘要对象中的一个,我就可以使用该ID来获取实际的模型对象,并执行事务操作和提交更改。
因此,对于您的情况,我会为所有三个实体提供这些专门的搜索存储库,当用户希望对其中一个实体执行和操作时,您总是获取它所属的计划聚合。
通过这种方式,您可以执行搜索,同时仍然使用所需的不变量维护您的单个聚合。
编辑-示例:
好吧,我猜实现是主观的,但这就是我在我的应用程序中处理它的方式,以“TeamMember”聚合为例。用C#编写的示例。我有两个类库:
Model库包含aggregate类,并强制执行所有不变量,Reporting库包含以下简单类:
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; }
}报告库还包含以下接口:
public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary>
{
}这是应用层(在我的例子中恰好是WCF服务)将使用的接口,并将通过我的IoC容器(Unity)解析实现。IReportRepository位于Infrastructure.Interface库中,基础ReportRepositoryBase也是如此。因此,我的系统中有两种不同类型的存储库-聚合存储库和报告存储库……
然后在另一个库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);
}
}发布于 2010-04-10 20:30:29
这里真正的问题是SRP违规。应用程序的输入部分与输出部分冲突。
坚持使用第一个解决方案(Plan==aggregate根)。人为地推动实体(甚至价值对象)聚合根,会扭曲整个域模型,并破坏一切。
您可能希望检查所谓的CQRS (命令、查询、责任、隔离)体系结构,该体系结构非常适合解决此特定问题。Here's an example app作者: Mark Nijhof。这是一个很好的'getting-started'列表。
发布于 2010-04-14 23:26:23
这就是CQRS体系结构的全部要点:将修改域的命令从查询中分离出来,这些命令只是给出域状态的视图,因为对命令和查询的要求是如此不同。
你可以在这些博客上找到一个很好的介绍:
还有很多其他的博客(包括mine)
https://stackoverflow.com/questions/2558469
复制相似问题