6

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

我有 3 个实体:Plan、PlannedRole 和 PlannedTraining。每个计划可以包括许多 PlannedRoles 和 PlannedTrainings。

解决方案 1:起初我认为 Plan 是聚合根,因为 PlannedRole 和 PlannedTraining 在计划的上下文之外没有意义。他们总是在一个计划之内。此外,我们有一条业务规则,规定每个计划最多可以有 3 个 PlannedRoles 和 5 个 PlannedTrainings。所以我想通过将计划指定为聚合根,我可以强制执行这个不变量。

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

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

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

还有另一个不变量表明计划只有在它具有特定状态时才能更改。因此,我应该无法将任何 PlannedRoles 或 PlannedTrainings 添加到不在该状态的计划中。同样,我无法使用第二种方法强制执行此不变量。

任何建议将不胜感激。

干杯,莫什

4

3 回答 3

9

在设计我的模型时,我遇到了类似的问题,并提出了这个我认为可能对你有帮助的问题,尤其是关于你的第一点。

DDD - 如何实现用于搜索的高性能存储库

在搜索方面,我不使用“模型”,而是使用专门的搜索存储库返回“摘要”对象......即“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; }
}

报告库还包含以下接口:

public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary>
{

}

这是应用程序层(在我的例子中恰好是 WCF 服务)将使用的接口,并将通过我的 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);
        }
    }
于 2010-04-02T20:23:53.193 回答
4

这里真正的问题是违反 SRP。您的应用程序的输入部分与输出发生冲突。

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


您可能想查看所谓的CQRS(命令查询职责分离)架构,该架构非常适合解决此特定问题。这是Mark Nijhof的示例应用程序。这是很好的“入门”列表。

于 2010-04-10T12:30:29.487 回答
3

这就是CQRS架构的全部要点:将修改域的命令与查询隔离开来,这些命令只是给出域状态的视图,因为对命令和查询的要求是如此不同。

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

在许多其他博客上(包括我的

于 2010-04-14T15:26:22.837 回答