在CQRS + ES系统中存储和更新读取模型

时间:2013-10-18 04:30:23

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

背景

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

如果创建了问题或帖子,则相当直接

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

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

您如何处理评论等关系?

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

这让我认为我应该将评论自己建模为他们自己的聚合根。这样,发布到博客帖子或问题的评论不会导致与问题本身发生冲突。

因此,假设我以这种方式将写入方面的注释建模为聚合,我有两个问题;

1)写入方面的问题或帖子聚合是否仍然需要存储此关系?注释聚合本身已经存储了哪个项目也是通过id引用发布的。

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

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

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

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

2)在阅读方面,存储此数据的最佳方式是什么?

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

我在那里考虑在问题表中添加一个列,例如将所有注释存储为序列化的json clob或其他东西。这样,当更改进入注释时,我仍然可以提取一条记录来加载问题,对注释进行适当的更改(例如更新现有注释,添加新注释或删除注释)并重新保存记录。从阅读的角度来看,仍然可以一次性检索整个问题。

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

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

3 个答案:

答案 0 :(得分:2)

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

存储阅读模型的最佳方式是非常容易查询。文档数据库可以是一个很好的技术解决方案,但它也适用于rdbms,只要您定义了相关的读取模型模式。

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

答案 1 :(得分:0)

问题1:无需在问题中建立关系。这里没有特别的一致性保护。

问题2:我最近在读NoSQL。像Casandra这样的Column-Family数据库似乎适合评论。

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

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

<强>更新

  

是注释列只是一个序列化的id集合,   评论完整?   没有评论作为列存储在一行中。 Casandra支持嵌套列。所以评论栏可能有这样的结构

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

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

答案 2 :(得分:-1)

问题1: 您不能处理Aggregate根目录中的事件。打破DDD原则是一个坏主意。如果Comments位于不同的聚合中,则问题聚合中的任何后果必须最终由您域中的某种流程管理器,域服务或Saga处理。

如果可能在您的域中,您必须声明问题不知道评论(我猜这是一种自然的思维方式),所以您不应该保留任何类型的参考。

另一方面的评论可以引用与之相关的问题。

问题2:为什么不在“评论”表中保留发布/发布所需的所有字段(处理问题/发布更新)?这使您无需在查询读取模型时加入这两个表格。