orm - 如何避免聚合依赖于外部包含?
问题描述
我不使用延迟加载。我的根聚合有实体(集合导航属性)。我希望我的聚合是自包含的,对自己负责,并遵循单一责任原则(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
and之类的方法的存储库GetBlogWithPosts
会很丑陋。我是否必须创建一个存储库方法,例如GetBlog
始终包含所有子实体?(这将是一个大而慢的查询,会超时)。
这个问题有什么解决办法吗?
解决方案
我意识到这可能是一个实践领域,但我认为没有充分讨论的重要一点是,不应该总是应用严格的 DDD。DDD 带来了一定的复杂性,以尽量减少复杂性的爆炸。如果一开始的复杂性很小,那么增加前期的复杂性是不值得的。
正如评论中提到的,聚合是一个一致性边界。由于似乎没有强制执行任何一致性,您可以拆分它。Blog
可以有一个集合PostRef
或其他东西,所以它不需要拉回可能有 ID 和标题的所有Post
数据?PostRef
然后Post
是它自己的聚合。我猜Post
有一个Author
. 建议不要在不是聚合根的其他聚合中引用实体,所以现在看起来Author
s 不应该在Blog
.
当您的起点是 ORM 时,我的经验是您的模型将与 DDD 建议作斗争。创建你的模型,然后看看如何持久化你的聚合。在这一点上,我和许多其他人的经验是,ORM 不值得它在整个项目中带来的牦牛剃须。对于不了解约束的人来说,添加不应该存在的引用也太容易了。
解决性能问题。请记住,您的读写模型不必相同。您优化您的写入模型以强制执行约束。如果分开,您可以优化您的读取模型以提高查询性能。如果这对您来说听起来像 CQRS,那么您是对的。尽管如此,移动部件的数量增加了,它应该解决的问题比它引入的更多。同样,您的 ORM 将在这方面与您抗争。
最后,如果您确实有需要大量数据的一致性约束,您需要问一个问题:它们是否真的需要实时执行?当您开始建模时,会出现一些新选项。
SubmittedPost
->RejectedPost
或AcceptedPost
-> PublishedPost
。如果这是作为后台进程发生的,则需要提取的数据量不会影响 UX。如果这听起来很有趣,我建议你看一看这本很棒的书《Domain Modeling made Functional》。
其他一些资源:
推荐阅读
- javascript - NativeScript Vue - 将焦点放在 TextField 上
- r - 如何在 ggplot 中修改我的 xaxis 以显示日期标签
- python - 一个巨大的嵌套列表与另一个巨大的嵌套列表的元素的近似比较
- javascript - 如何制作js对象并动态保存到页面
- c++ - R中的Dulmage-Mendelsohn分解
- java - 使用方法参考在 Vavr 中尝试
- .htaccess - Htaccess 在条件下用问号重写
- javascript - 表构建后注入锚标记
- java - Scala:反射 API 调用具有相同名称的两种方法之一
- dataframe - 如何在 Julia 中重塑和绘制 DataFrame?