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

Domain driven design 在CQRS+中存储和更新读取模型;ES系统,domain-driven-design,cqrs,event-sourcing,Domain Driven Design,Cqrs,Event Sourcing,背景 我有一个使用CQRS+ES的系统,在这个系统中有一些聚合,例如博客文章或问题,它们被持久化在事件存储中,并将事件发送到查询端,以通过投影持久化读取模型 对于正在创建的问题或帖子,这是一个相当直截了当的过程 客户端创建命令以创建新问题 命令处理程序创建新的问题聚合并将更改保存在事件存储中 当聚合应用更改时,它会触发IssueCreatedEvent或类似命令 读取端的投影将监听此消息,并创建问题模型和它想要的任何其他非规范化数据(例如用于查询所有问题列表的缩减问题列表) 如果对问题进行了

背景

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

对于正在创建的问题或帖子,这是一个相当直截了当的过程

  • 客户端创建命令以创建新问题
  • 命令处理程序创建新的问题聚合并将更改保存在事件存储中
  • 当聚合应用更改时,它会触发IssueCreatedEvent或类似命令
  • 读取端的投影将监听此消息,并创建问题模型和它想要的任何其他非规范化数据(例如用于查询所有问题列表的缩减问题列表)
如果对问题进行了更改,并且在写入端引发了适当的事件,如IssueStatus,则在读取端相应地进行了更改和处理。在读取端加载两个非标准化模型,从事件中更新状态并保存。简单

您如何处理评论等关系?

我正在实现一个评论系统,用户可以在其中对某个问题或博客文章发表评论。我的第一个想法是将这些评论添加到问题中,或者在写文章时添加到帖子聚合中,以保持一致性。当我想到这一点时,我意识到这可能会带来很多不必要的并发问题,比如当有人更新问题,而其他人来发布新评论时

这让我想到,我应该将注释本身建模为它们自己的聚合根。这样,发表在博客帖子或问题上的评论不会与问题本身产生冲突

因此,假设我以这种方式将写端的评论建模为聚合,我有两个问题

1)写入端的发布或发布聚合是否仍需要存储此关系?注释聚合本身已经存储了它发布的带有id引用的项目

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

      public void Handle(CommentCreatedEvent @event)
      {
         _Comments.Add(@event.AggregateId)
      }
}
如果是这样,我正在考虑让问题聚合订阅CommentCreated事件并添加自己的引用

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

      public void Handle(CommentCreatedEvent @event)
      {
         _Comments.Add(@event.AggregateId)
      }
}
public类问题:AggregateRoot,IEventHandler
{
私人收集意见;
公共无效句柄(CommentCreatedEvent@event)
{
_Comments.Add(@event.AggregateId)
}
}
由于注释已经存储了对其父级的引用,这是否足够?在写端实际上并不需要这些数据,而在读端,当父级加载了所有注释时,这些数据更为重要

Row | issueId | name                          | comments   |
    | 1       | comments persistence solution | {c1,c2,c3} | 
2)在读取端,存储此数据的最佳方式是什么?

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

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

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


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

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

问题2:我最近在读NoSQL。像卡桑德拉这样的列族数据库似乎适合评论

Row | issueId | name                          | comments   |
    | 1       | comments persistence solution | {c1,c2,c3} | 
您可以使用Casandra api或Casandra查询语言来检索注释子集或整个注释列

更新

注释列只是ID的序列化集合吗 完整的评论? 否注释作为列存储在一行中。Casandra支持嵌套列。所以comments列的结构可能如下


如果我没弄错的话,你可以在卡桑德拉单独获取和设置任何评论。在这种情况下,您可以更新任何一条注释。或者你可以在评论栏中检索所有评论。

我在聚会上迟到了一点,不过,下面是我对第二条的看法

Row | issueId | name                          | comments   |
    | 1       | comments persistence solution | {c1,c2,c3} | 
存储读取模型的最佳方式是以一种非常容易查询的方式。文档数据库可以是一个很好的技术解决方案,但它也可以与rdbms一起使用,前提是您定义了相关的读取模型模式

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

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

如果可能的话,在你的领域中,你必须声明这个问题不知道评论(我想这是一种自然的思维方式),所以你不应该保留任何评论