【问题标题】:Storing and updating read model in a CQRS + ES system在 CQRS + ES 系统中存储和更新读取模型
【发布时间】:2013-10-26 19:44:25
【问题描述】:

背景

我有一个使用 CQRS + ES 的系统,在这个系统中,有一些聚合,例如博客文章或问题,它们会持久保存在事件存储中,并将事件发送到查询端以通过投影来持久保存读取模型。

在创建问题或帖子的情况下,这是相当简单的

  • 客户端创建命令以创建新问题
  • 命令处理程序创建一个新的问题聚合并将更改保存在事件存储中
  • 当聚合应用更改时,它会触发 IssueCreatedEvent 或类似事件
  • 读取端的投影将监听此内容并创建问题模型和任何其他所需的非规范化数据(例如用于查询所有问题列表的缩减 IssueListItem)

如果对问题进行了更改,并且在写入端引发了适当的事件,例如 IssueStatusChanged,并在读取端进行了相应的处理。在读取端加载两个非规范化模型,更新事件的状态并保存。很简单。

你们如何处理诸如cmets之类的关系?

我正在实施一个评论系统,用户可以在其中发布有关问题或博客文章的 cmets。我的第一个想法是将这些 cmets 添加到问题中或在写入端发布聚合以保持一致性。当我想到这一点时,虽然我意识到这可能会引入许多不必要的并发问题,例如当有人更新问题并且其他人来发表新评论时。

这使我认为我应该将 cmets 本身建模为它们自己的聚合根。这样,发布到博客文章或问题的 cmets 不会与问题本身发生冲突。

所以假设我以这种方式将写入端的 cmets 建模为聚合,我有两个问题;

1) 写入端的问题或帖子聚合是否还需要存储这种关系? 评论聚合本身已经存储了它发布的哪个项目以及一个 id 引用。

如果是这样,我正在考虑让问题聚合订阅评论创建事件并添加自己的引用。

public class Issue : AggregateRoot, IEventHandler<CommentCreatedEvent>
{
      private ICollection<Guid> _Comments;

      public void Handle(CommentCreatedEvent @event)
      {
         _Comments.Add(@event.AggregateId)
      }
}

这是否足够或不需要,因为评论已经存储了对其父级的引用?这些数据在写入端并不真正需要,但在读取端更重要,因为它是加载所有 cmets 的父级。

2) 在读取方面,存储这些数据的最佳方式是什么?

具体来说,为了使这些数据易于更新,我需要为 cmets 放入另一个表并将它们加入到相应的帖子或问题中。在我完成cmets 之后,我将实施一个following 系统,用户可以在其中关注项目以接收更新。然而,沿着这条路走下去会很快让我回到读取端的高度规范化模式,这违背了优化、非规范化读取模型的目的。

因此,我正在考虑在问题表中添加一个列,例如将所有 cmets 存储为序列化的 json clob 或其他内容。这样,当 cmets 发生更改时,我仍然可以提取一条记录来加载问题,对 cme​​ts 进行适当的更改(例如更新现有评论、添加新评论或删除评论)并重新保存记录.从阅读的角度来看,仍然可以一口气检索到整个问题。

我看到这种方法的问题是,例如,如果用户更改了他们的个人资料图片或个人资料名称,我将不得不加载每个问题和/或帖子,加载 cmets 并在评论信息。

我还想知道文档数据库(我一直在为读取方面考虑的其他东西)如何解决更新嵌套数据的问题?

【问题讨论】:

    标签: domain-driven-design cqrs event-sourcing


    【解决方案1】:

    不过,我在派对上有点晚了,这是我对 2 号的看法。

    存储读取模型的最佳方式是易于查询。文档数据库可以是一个很好的技术解决方案,但它也可以与 rdbms 一起使用,前提是您定义了相关的读取模型模式。

    您可以将所有 cmets 与帖子一起存储,但情况并非总是如此,因为高流量网站通过 ajax 将 cmets 与帖子分开加载。所以这真的取决于读取模型用例。

    【讨论】:

    • 很好的结论“存储读取模型的最佳方式是易于查询”。
    • 我同意 MikeSW 的观点,保持简单易用。无论如何,阅读模型是“一次性英雄”,必须满足您的要求。
    【解决方案2】:

    问题1:不需要有Issue中的关系。这里没有特别要保护的一致性。

    问题 2:我最近正在阅读 NoSQL 提炼。看来像Casandra这样的Column-Family数据库适合cmets。

    Row | issueId | name                          | comments   |
        | 1       | comments persistence solution | {c1,c2,c3} | 
    

    您可以使用 Casandra api 或 Casandra 查询语言来检索 cmets 的子集或整个 cmets 列。

    更新

    是 cmets 列只是一个序列化的 id 集合吗? cmets 整体? 没有 cmets 存储为一行中的列。 Casandra 支持嵌套列。所以 cmets 列可能有这样的结构

    | other columns | comments                   |
    |  ............ | c1   | c2           | c3   |
                    | "+1" | "Nice one"   | "+1" |
    

    如果我没记错的话,你可以在 Casandra 中单独获取和设置任何评论。在这种情况下,您可以更新任何一条评论。或者您可以获取 cmets 列来检索所有 cmets。

    【讨论】:

    • 问题1)谢谢。这就是我所倾向于的。由于我的大部分问题都在 2 左右,您能否进一步详细说明/扩展您的答案?不太确定您所说的列族数据库是什么意思,并且 cmets 列只是一个序列化的 id 集合,即整个 cmets 吗?我不确定这如何解决我关于更新 cmets、评论作者信息等的问题。如果可能的话,我真的不想将自己绑定到特定的持久性框架和/或 api。谢谢。
    • @JoshuaHayes 对不起,我没有让自己理解,我不是这方面的专家。请查看我的更新。
    • 嗨河马。我将看看 Casandra,但我真的在寻找一种更通用的解决方案来存储和更新读取模型中的非规范化数据,而不是使用像 Casandra 这样的其他框架。不过我很欣赏这个建议。
    【解决方案3】:

    问题 1: 您不能在聚合根中处理事件。违反 DDD 原则是个坏主意。如果 Comments 存在于不同的聚合中,那么 Issue 聚合中的任何后果最终都必须由您域中的某种流程管理器、域服务或 Saga 处理。

    如果可能在您的域中,您必须声明 Issue 不知道 Comments(我想这是一种自然的思维方式),因此您不应保留任何此类引用。

    其他方式的评论可以保留对他们相关问题的引用。

    问题 2:为什么不将问题/帖子所需的所有字段保留在评论表中(处理问题/帖子更新)?这使您在查询读取模型时无需连接这两个表。

    【讨论】:

    • "你不能在你的 AR 中处理事件。这是一个违反 DDD 原则的坏主意" - 澄清一下,我假设你的意思是它不能处理 另一个 AR的事件?如果 AR 无法处理自己的事件,那么它如何更新自己的状态?
    猜你喜欢
    • 2012-04-30
    • 2017-05-12
    • 2021-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多