首页 > 解决方案 > 如何避免聚合依赖于外部包含?

问题描述

我不使用延迟加载。我的根聚合有实体(集合导航属性)。我希望我的聚合是自包含的,对自己负责,并遵循单一责任原则(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 应用程序,并且由于执行多个查询而性能更差。我觉得拥有一个包含诸如 GetBlogWithAuthorsand之类的方法的存储库GetBlogWithPosts会很丑陋。我是否必须创建一个存储库方法,例如GetBlog始终包含所有子实体?(这将是一个大而慢的查询,会超时)。

这个问题有什么解决办法吗?

标签: ormentity-framework-coredomain-driven-designddd-repositoriesaggregateroot

解决方案


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

正如评论中提到的,聚合是一个一致性边界。由于似乎没有强制执行任何一致性,您可以拆分它。Blog可以有一个集合PostRef或其他东西,所以它不需要拉回可能有 ID 和标题的所有Post数据?PostRef

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

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

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

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

其他一些资源:


推荐阅读