【发布时间】: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 应用程序,并且由于执行多个查询而性能更差。我觉得拥有一个包含GetBlogWithAuthors 和GetBlogWithPosts 等方法的存储库会很丑陋。我是否必须创建一个存储库方法,例如GetBlog,它始终包含所有子实体? (这将是一个大而慢的查询,会超时)。
有没有办法解决这个问题?
【问题讨论】:
-
聚合是一致性边界。因此,它应该包含通过域规则保持一致性所需的所有数据。您是否考虑过对聚合进行垂直分区并使用两个不同的存储库创建两个不同的聚合?
-
不,我没有,但也许我应该这样做。但是聚合有多个子实体不是很常见吗?并且用户仅与仅使用其中一个子实体的聚合进行交互?此外,我觉得博客包含作者和帖子,因此应该是一个集合。这只是一个例子。我不知道任何分区策略。
-
我正在为您的问题提出解决方案。您的聚合是否应该拆分取决于您的用例。您描述了永远不会同时需要作者和帖子的场景,而且您不想为每个用例都加载两者。如果您有两种情况都需要的情况,或者您可以加载完整的聚合根,那么分区不是要走的路。 HTH
-
好吧,作者和帖子都是构成博客的一部分,因此它们在概念上是相关的,并且属于同一域 (DDD)。他们属于一起。这并不意味着我总是想加载两者,因为我可能只想操作其中一个实体。
标签: orm entity-framework-core domain-driven-design ddd-repositories aggregateroot