【问题标题】:How to avoid aggregate being dependent on outside includes?如何避免聚合依赖于外部包含?
【发布时间】:2020-03-29 08:01:23
【问题描述】:

我不使用延迟加载。我的根聚合有实体(集合导航属性)。我希望我的聚合是自包含的,对自己负责,并遵循单一责任原则(SRP),并坚持高内聚和低耦合。

问题在于检索根聚合的代码需要包含某些子实体,具体取决于它希望与聚合交互的方式。

例子:

public class Blog // My root aggregate
{
    public ICollection<Author> Authors { get; set; }
    public ICollection<Post> Posts { get; set; }

    public AddAuthor(Author author)
    {
        _authors.Add(author);
    }

    public AddPost(Post post)
    {
        _posts.Add(post);
    }
}

如果我想添加作者,我必须这样做:

var blog = _context.Blogs.Include(x => x.Authors).Single(x => x.BlogId == 1);
blog.AddAuthor(/* ... */);

如果我想添加帖子,我必须这样做:

var blog = _context.Blogs.Include(x => x.Posts).Single(x => x.BlogId == 1);
blog.AddPost(/* ... */);

但是我觉得这破坏了封装,因为现在我的博客聚合不是自包含的,它的功能取决于调用者如何从 DbContext(或存储库)检索聚合。如果调用者未包含必要的依赖实体,则聚合操作将失败(因为该属性将为空)。

我想避免延迟加载,因为它不太适合 Web 应用程序,并且由于执行多个查询而性能更差。我觉得拥有一个包含GetBlogWithAuthorsGetBlogWithPosts 等方法的存储库会很丑陋。我是否必须创建一个存储库方法,例如GetBlog,它始终包含所有子实体? (这将是一个大而慢的查询,会超时)。

有没有办法解决这个问题?

【问题讨论】:

  • 聚合是一致性边界。因此,它应该包含通过域规则保持一致性所需的所有数据。您是否考虑过对聚合进行垂直分区并使用两个不同的存储库创建两个不同的聚合?
  • 不,我没有,但也许我应该这样做。但是聚合有多个子实体不是很常见吗?并且用户仅与仅使用其中一个子实体的聚合进行交互?此外,我觉得博客包含作者和帖子,因此应该是一个集合。这只是一个例子。我不知道任何分区策略。
  • 我正在为您的问题提出解决方案。您的聚合是否应该拆分取决于您的用例。您描述了永远不会同时需要作者和帖子的场景,而且您不想为每个用例都加载两者。如果您有两种情况都需要的情况,或者您可以加载完整的聚合根,那么分区不是要走的路。 HTH
  • 好吧,作者和帖子都是构成博客的一部分,因此它们在概念上是相关的,并且属于同一域 (DDD)。他们属于一起。这并不意味着我总是想加载两者,因为我可能只想操作其中一个实体。

标签: orm entity-framework-core domain-driven-design ddd-repositories aggregateroot


【解决方案1】:

我意识到这可能是一个实践领域,但我认为没有充分讨论的重要一点是不应该总是应用严格的 DDD。 DDD 带来了一定的复杂性,以尽量减少复杂性的爆炸。如果一开始的复杂性很小,那么前期增加的复杂性是不值得的。

正如 cmets 中提到的,Aggregate 是一个一致性边界。由于似乎没有强制执行任何一致性,您可以拆分它。 Blog 可以有 PostRef 或其他东西的集合,因此它不需要撤回所有 Post 数据,其中 PostRef 可能有 Id 和 Title?

那么Post 是它自己的聚合。我猜Post 有一个Author。建议不要在不是聚合根的其他聚合中引用实体,所以现在看来​​ Authors 不应该在 Blog 中。

当您的起点是 ORM 时,我的经验是您的模型将与 DDD 建议作斗争。创建你的模型,然后看看如何持久化你的聚合。在这一点上,我和其他许多人的经验是,ORM 不值得它在整个项目中带来的牦牛剃须。对于不了解约束的人来说,添加不应该存在的引用也太容易了。

解决性能问题。请记住,您的读写模型不必相同。您优化您的写入模型以强制执行约束。如果分开,您可以优化您的读取模型以提高查询性能。如果这对您来说听起来像 CQRS,那么您是对的。但是,移动部件的数量再次增加,它应该解决的问题比它引入的更多。同样,您的 ORM 会在这方面与您抗衡。

最后,如果您确实有需要大量数据的一致性约束,您需要问一个问题:它们是否真的需要实时执行?当您开始建模时,会出现一些新选项。 SubmittedPost -> RejectedPostAcceptedPost -> PublishedPost。如果这是作为后台进程发生的,则需要提取的数据量不会影响 UX。如果这听起来很有趣,我建议你看看这本很棒的书《Domain Modeling made Functional》。

其他一些资源:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-06
    • 2013-11-05
    • 1970-01-01
    • 2014-12-08
    • 1970-01-01
    • 1970-01-01
    • 2020-02-13
    • 2017-04-25
    相关资源
    最近更新 更多