代码之家  ›  专栏  ›  技术社区  ›  Mosh

ddd:聚合根

  •  6
  • Mosh  · 技术社区  · 15 年前

    我需要帮助找到我的聚合根和边界。

    我有三个实体:计划、计划角色和计划培训。每个计划可以包括许多计划的角色和计划的培训。

    解决方案1:首先,我认为计划是总的根源,因为计划角色和计划培训在计划的上下文中没有意义。他们总是在计划之内。此外,我们有一个业务规则,即每个计划最多可以有3个计划角色和5个计划培训。所以我认为通过将计划指定为聚合根,我可以强制执行这个不变量。

    但是,我们有一个搜索页面,用户在其中搜索计划。结果显示了计划本身的一些特性(没有计划的角色或计划的培训)。我想如果我必须加载整个聚合,它会有很多开销。有近3000个计划,每个计划可能有几个孩子。将所有这些对象加载到一起,然后忽略搜索页面中的计划角色和计划培训对我来说没有意义。

    解决方案2:我刚刚意识到用户还需要两个搜索页面,在那里他们可以搜索计划的角色或计划的培训。这让我意识到他们试图独立地访问这些对象,并且“脱离”计划的上下文。所以我认为我的最初设计是错的,这就是我提出这个解决方案的原因。所以,我认为这里有3个集合,每个实体一个。

    这种方法使我能够独立地搜索每个实体,并在解决方案1中解决性能问题。但是,使用这种方法,我不能强制执行前面提到的不变量。

    还有另一个不变量,它表示只有当计划处于某种状态时,才能对其进行更改。所以,我不应该把任何计划的角色或计划的培训添加到一个不在那个状态的计划中。同样,我不能用第二种方法来强制这个不变量。

    任何建议都将不胜感激。

    干杯, 莫什

    3 回复  |  直到 15 年前
        1
  •  9
  •   Community CDub    8 年前

    在设计我的模型时,我也遇到了类似的问题,我问了这个问题,我认为可能会对你有所帮助,特别是关于你的第一点。

    DDD - How to implement high-performing repositories for searching .

    在搜索方面,我不使用“模型”,而是使用返回“摘要”对象的专业搜索存储库…即“计划摘要”。这些仅仅是信息对象(可以认为更像是报告),并没有在事务性意义上使用-我甚至没有在我的模型类库中定义它们。通过创建这些专用存储库和类型,我可以实现高性能的搜索查询,这些查询可以包含分组数据(例如计划的训练计数),而无需在内存中加载聚合的所有关联。一旦用户在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; }
    }
    

    报表库还包含以下界面:

    public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary>
    {
    
    }
    

    这是应用层(在我的例子中恰好是wcf服务)将使用的接口,它将通过我的ioc容器(unity)来解决实现问题。IReportRepository与Base 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);
            }
        }
    
        2
  •  4
  •   Arnis Lapsa    15 年前

    这里真正的问题是srp违规。应用程序的输入部分与输出冲突。

    坚持第一个解决方案(plan==聚合根)。人为地提升实体(甚至是值对象)来聚合根会扭曲整个域模型并破坏一切。


    你可能想看看所谓的 CQRS (命令查询职责分离)完全适合解决此特定问题的体系结构。 Here's an example app 马克·尼霍夫。这里很好 'getting-started' 名单。

        3
  •  3
  •   thinkbeforecoding    15 年前

    这就是 小精灵 体系结构:将修改域的命令与简单地给出域状态视图的查询隔离开来,因为对命令和查询的需求是如此不同。

    你可以在这些博客上找到很好的介绍:

    在许多其他博客上(包括 mine )

    推荐文章