背景
我有一个使用 CQRS + ES 的系统,在这个系统中,有一些聚合,例如博客文章或问题,它们保存在事件存储中,并将事件发送到查询端以通过投影来保存读取模型。
在创建问题或帖子的情况下,这是相当简单的
- 客户端创建命令以创建新问题
- 命令处理程序创建一个新的问题聚合并将更改保存在事件存储中
- 当聚合应用更改时,它会触发 IssueCreatedEvent 或类似事件
- 读取端的投影将监听这一点并创建一个问题模型和它想要的任何其他非规范化数据(例如用于查询所有问题列表的缩减 IssueListItem)
如果对问题进行了更改,并且在写入端引发了适当的事件,例如IssueStatusChanged,并在读取端进行了相应的处理。在读取端加载两个非规范化模型,更新事件的状态并保存。简单的。
你如何处理评论等关系?
我正在实现一个评论系统,用户可以在其中发布对问题或博客文章的评论。我的第一个想法是将这些评论添加到问题中或在写入端发布聚合以保持一致性。当我想到这一点时,我意识到这可能会引入许多不必要的并发问题,例如当有人更新问题而其他人来发表新评论时。
这使我认为我应该将评论本身建模为它们自己的聚合根。这样,发布到博客文章或问题的评论不会与问题本身发生冲突。
因此,假设我以这种方式将写入端的评论建模为聚合,我有两个问题;
1)写端的issue或post聚合是否还需要存储这个关系?评论聚合本身已经使用 id 引用存储了它发布的项目。
如果是这样,我正在考虑让问题聚合订阅评论创建事件并添加自己的引用。
public class Issue : AggregateRoot, IEventHandler<CommentCreatedEvent>
{
private ICollection<Guid> _Comments;
public void Handle(CommentCreatedEvent @event)
{
_Comments.Add(@event.AggregateId)
}
}
由于评论已经存储了对其父级的引用,这是否足够或不需要?这些数据在写入端并不是真正需要的,当它是加载所有评论的父级时,在读取端更重要。
2)在读取方面,存储这些数据的最佳方式是什么?
具体来说,为了使这些数据易于更新,我需要在另一个表中放置评论并将它们加入到适当的帖子或问题中。在我完成评论后,我将实施一个关注系统,用户可以在其中关注项目以接收更新。然而,沿着这条路走下去会很快让我回到读取端的高度规范化模式,这违背了优化、非规范化读取模型的目的。
因此,我考虑在问题表中添加一个列,例如将所有评论存储为序列化的 json clob 或其他内容。这样,当评论发生更改时,我仍然可以拉出一条记录来加载问题,对评论进行适当的更改(例如更新现有评论、添加新评论或删除评论)并重新保存记录. 从阅读的角度来看,仍然可以一口气检索整个问题。
我在这种方法中看到的问题是,如果用户更改了他们的个人资料图片或个人资料名称,我将不得不加载每个问题和/或帖子,加载评论并在评论信息中进行适当的更改。
我还想知道文档数据库(我一直在为读取端考虑的其他东西)如何解决更新嵌套数据的问题?