一个聚合根可以包含嵌套的子项吗?

时间:2012-08-21 09:30:37

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

两个月前,我买了Scott Millet的“Professional ASP.NET Design Patterns”一书,因为我想学习如何使用设计模式构建一个分层的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);

2 个答案:

答案 0 :(得分:1)

聚合根当然可以包含嵌套的子节点,但是如果这些子节点也是聚合,则可能是一个警告,可能聚合过多。在您的情况下,我认为Collection是一个聚合而Category不是,它只是一个实体,甚至是属于Collection聚合的值对象,它碰巧包含{{1实例也是值对象。

我会更改实现,以便Keyword服务方法看起来更像这样:

CreateCategory

public void CreateCategory(CreateCategoryRequest request) { var collection = _collectionRepository.Get(request.IdentityToken, request.CollectionName); collection.AddCategory(request.CategoryName); _uow.Commit(); } 上的AddCategory方法负责创建Collection实例以及错误检查。这是有道理的,因为它是聚合根,它负责管理它包含的实体和值对象的集群。在存储库上没有调用Add方法,因为环境工作单元应该提交更改。

Category方法我会改为看起来更像:

CreateKeyword

public void CreateKeyword(CreateKeywordRequest request) { var collection = _collectionRepository.Get(request.IdentityToken, request.CollectionName); collection.AddKeyword(request.CategoryName, request.KeywordName, request.KeywordDescription); _uow.Commit(); } 上的AddKeyword方法检索相应的Collection,然后向其添加关键字,如果需要,则会抛出异常以强制执行一致性和有效性。

正如您所看到的,这两种方法有一种模式 - 首先通过密钥检索聚合,然后调用聚合上的方法,最后提交所有内容。通过这种方式,聚合可以更好地控制自己的状态,避免使用anemic domain model以及减少服务中存在的代码量。

要深入处理骨料设计,请查看Effective Aggregate Design by Vaughn Vernon

答案 1 :(得分:0)

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

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

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