【问题标题】:Can one aggregate root contain nested childrens?一个聚合根可以包含嵌套的子节点吗?
【发布时间】:2012-08-21 09:30:37
【问题描述】:

两个月前,我购买了 Scott Millet 的“专业 ASP.NET 设计模式”一书,因为我想学习如何使用设计模式构建分层的 Web 应用程序。我在自己的应用程序中使用了本书中的案例研究,所以一切都设置好了。

我的问题是我不确定我的聚合根。

我有一个可以创建集合的用户。用户可以将类别添加到集合中,并将关键字添加到类别中。在我的数据库中看起来像这样:

- Users
    - PK: UserId

- Collections
    - PK: CollectionId
    - FK: UserId

- Categories
    - PK: CategoryId
    - FK: CollectionId

- Keywords
    - PK: KeywordId
    - FK: CategoryId

我不认为将用户作为集合的聚合根是合乎逻辑的,但是类别和关键字共同构成了一个集合。所以我让用户成为一个还没有孩子的聚合根,并收集一个聚合根。一个集合可以有多个类别,类别可以有多个关键字。所以当我想添加一个类别时,我会这样做:

public void CreateCategory(CreateCategoryRequest request)
    {
        Collection collection = _collectionRepository.FindCollection(request.IdentityToken, request.CollectionName);

        Category category = new Category { Collection = collection, CategoryName = request.CategoryName };

        ThrowExceptionIfCategoryIsInvalid(category);

        collection.AddCategory(category);

        _collectionRepository.Add(collection);
        _uow.Commit();
    }

效果很好,但是当我想添加关键字时,我首先需要获取集合,然后获取可以添加关键字的类别,然后提交集合:

public void CreateKeyword(CreateKeywordRequest request)
    {
        Collection collection = _collectionRepository.FindCollection(request.IdentityToken, request.CollectionName);

        Category category = collection.Categories.Where(c => c.CategoryName == request.CategoryName).FirstOrDefault();

        Keyword keyword = new Keyword { Category = category, KeywordName = request.KeywordName, Description = request.KeywordDescription };

        category.AddKeyword(keyword);

        _collectionRepository.Add(collection);
        _uow.Commit();
    }

这只是感觉不对(是吗?)是什么让我相信我应该将类别作为关键字的总根。但这提出了另一个问题:我有一个集合聚合,它像我在第一个代码示例中所做的那样创建一个类别聚合,这仍然有效吗?示例:collection.Add(category);

【问题讨论】:

  • 在这种情况下,一个简单的经验法则是考虑“在集合之外有一个类别是否有意义?”如果没有,将类别作为单独的根并没有多大意义。
  • 我想到了这一点,因为我在一个页面上显示集合,在另一个页面上显示带有关键字的类别。但是即使它不是聚合根,通过类别对象添加关键字仍然有意义吗?
  • 然后考虑这个;该类别在集合之外具有身份是否有意义?即,如果两个用户添加同名的类别,它们是集合本地的还是用户之间共享的?
  • 它们在集合中是本地的。

标签: c# asp.net domain-driven-design


【解决方案1】:

聚合根当然可以包含嵌套的子级,但是如果这些子级也是聚合,则可能会警告聚合可能做得太多。在您的情况下,我认为 Collection 是一个聚合,而 Category 不是,它只是一个实体甚至是属于 Collection 聚合的值对象,它恰好包含 Keyword 实例,这些实例也是值对象.

我会更改实现,使CreateCategory 服务方法看起来更像这样:

public void CreateCategory(CreateCategoryRequest request)
{
        var collection = _collectionRepository.Get(request.IdentityToken, request.CollectionName);

        collection.AddCategory(request.CategoryName);

        _uow.Commit();
}

Collection 上的AddCategory 方法负责创建Category 实例以及错误检查。这是有道理的,因为它是聚合根,它负责管理它包含的实体集群和值对象。没有调用存储库上的 Add 方法,因为环境工作单元应该提交更改。

CreateKeyword 方法我将更改为看起来更像:

public void CreateKeyword(CreateKeywordRequest request)
{
        var collection = _collectionRepository.Get(request.IdentityToken, request.CollectionName);

        collection.AddKeyword(request.CategoryName, request.KeywordName, request.KeywordDescription);

        _uow.Commit();
}

Collection 上的 AddKeyword 方法检索适当的 Category,然后向其添加关键字,如果需要则抛出异常以强制一致性和有效性。

如您所见,这两种方法有一个模式——首先通过键检索聚合,然后调用聚合上的方法,最后提交所有内容。通过这种方式,聚合可以更好地控制自己的状态,避免出现anemic domain model 并减少服务中存在的代码量。

如需深入了解聚合设计,请查看Effective Aggregate Design by Vaughn Vernon

【讨论】:

  • 感谢您的清晰解释。我按照你的描述做了,效果很好。
  • 这是一种方法,但它也有缺点——例如,由于所有实体的所有 AddSomething() 方法,您必须将其塞入其中,因此具有臃肿的上帝对象聚合根。相反,您可能会意识到您的 CreateKeywordRequest 已经包含类别,因此您无需求助于 Collection 即可完成 Category.AddKeyword() 所需的一切 - 只是说。
  • 但是我的类别对象引用了一个集合来指示 NHbibernate 中的外键,而我的 CreateKeywordRequest 中没有。因此,当我想通过类别添加关键字时,我仍然需要该集合吗?我只有获取集合和类别的字符串。
  • @guillaume31- 我相信所有这些方法都是一件好事,因为它们实现了封装。但是,可能是聚合做的太多了。然后,可以使 Category 本身成为聚合。将 Category 设为聚合的原因也可能纯粹是技术性的,因为可能集合可能包含太多类别,无法在单个一致性边界中进行管理。
【解决方案2】:

这可能感觉不对,因为要添加一个新关键字,您从 Collection 引用开始,而您完全可以从 Category 引用开始。当用户添加关键字时,他肯定是在类别的上下文中这样做的,因此您当时可能在内存中拥有该类别。

另外,我没有在每个 Add[SomeSubEntity]() 方法的末尾得到 _collectionRepository.Add(collection)。 Collection 应该已经存在于存储库中,所以您应该做的就是保存它,对吧?

至于嵌套聚合,我觉得它有点复杂,与 2 个单独的聚合(类别和集合)相比,我并没有真正看到好处。

【讨论】:

  • 您对 _collectionRepository.Add(collection) 的看法是正确的。这没有意义。我仍然是一个初学者程序员,所以我可能读过一些东西。用户首先添加一个类别。创建一个关键字后,他或她可以选择是否添加关键字。所以我可以在集合存储库中创建一个返回类别的方法,我可以向该类别添加一个关键字。对吗?
  • 在我看来,您不需要返回类别的方法。只需保留新创建的 Category 实例并为其添加一个关键字。然后提交您的工作单元以将所有内容保存到数据库中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-08
  • 2021-05-11
  • 1970-01-01
  • 2010-12-16
  • 2019-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多