Domain driven design NoSql(Mongo DB)中的CQRS读取模型

Domain driven design NoSql(Mongo DB)中的CQRS读取模型,domain-driven-design,cqrs,Domain Driven Design,Cqrs,嗨,这是我第一次使用DDD/CQRS。我读过很多知识来源,但我还是有点困惑,也许有人能帮我:) 让我们假设一个简单的情况,即我们有产品和客户机(可能有不同的有界上下文)。 客户可以购买产品,他希望看到他购买的所有产品 在这种情况下,我意识到我需要一个UserPurchasesView视图模型,其中包含: purchaseId(是mongo主键) 用户ID 产品:{id、名称、图像、简短描述,[可能还有其他]} 奖品 时间戳 现在。。。问题是我的域正在生成一个类似于UserPurchasedP

嗨,这是我第一次使用DDD/CQRS。我读过很多知识来源,但我还是有点困惑,也许有人能帮我:)

让我们假设一个简单的情况,即我们有产品和客户机(可能有不同的有界上下文)。 客户可以购买产品,他希望看到他购买的所有产品

在这种情况下,我意识到我需要一个UserPurchasesView视图模型,其中包含:

  • purchaseId(是mongo主键)
  • 用户ID
  • 产品:{id、名称、图像、简短描述,[可能还有其他]}
  • 奖品
  • 时间戳
现在。。。问题是我的域正在生成一个类似于UserPurchasedProduct(userId,productId)的事件。我可以用奖品、产品名称或其他东西来丰富活动,但不是所有领域。我已经到了一个似乎是错误的地步

在这一点上,我意识到我需要类似于ProductDetailsView的东西:

  • productId(主键)
  • 奖品
  • 名字
  • 简短描述
  • 标志
此视图由以下事件维护:ProductCreated、ProductRename、ProductImageChanged

现在我们有两个选择

  • 当UserPurchasedProduct事件出现时,查看ProductDetails视图,获取所有需要的产品详细信息并将其保存在UserPurchasesView中,以便更快地读取。这个解决方案看起来不错,但它引入了一些额外的耦合,在我看来,这些视图在需要时无法很好地缩放。此外,在从事件存储中回复所有事件时,必须同时重建这两个视图(在这种情况下,重建也比较复杂)
  • 仅在UserPurchasesView中保留productId,并在用户查询其购买时读取多个视图。这是一些必须在某处进行的额外处理。在前端、后端控制器或某些读取模型的高级API中。更新:我还意识到,我还需要在UserPurchasesView中至少保留奖品和产品名称(以防发生变化),但有时需要购买时的价值,有时需要最近的价值。这种情况取决于一项业务,但我们可以想象两者兼而有之
  • 这些解决方案在我看来都不完美。是我错了,是我错过了什么,还是这只是一种方式?谢谢

    你很明白

    因此,您必须在读取模型之间的耦合和UI与单个读取模型之间的耦合之间进行选择

    CQRS/ES的一个主要优点是可以创建非常快速的读取模型(视图,如果您喜欢),而不需要任何连接,正如我所看到的,完美的缓存。我个人每次都选择了第一种方法,用完整的数据去规范化。视图非常快速,模型非常清晰如果您想优化应用程序的读取端(我认为您应该这样做),这是一个完美的解决方案。 通过收听正确的事件,您可以使这些读取模型与应用程序的其余部分保持同步。

    有第三个选项:

    负责UserPurchasesView视图的投影不仅侦听UserPurchasedProduct事件,还侦听ProductCreated、ProductRename、ProductImageChanged—影响UserPurchasesView的任何与产品相关的事件。现在,除了它负责的read模型的UserPurchasesView集合外,它还需要一个私有集合来维护它感兴趣的产品:({id、名称、图像、简短描述,[可能还有其他]}),这样当新的购买事件发生时,您可以从中获取这些产品字段的初始状态。由于您的UserPurchasesView无论如何都需要监听其中一些产品事件,以便在产品更改时保持最新,因此这实际上并没有太多额外的工作,并且避免了对其他投影(ProductDetailsView)的任何依赖。由于最终的一致性,交叉投影依赖关系也有一个潜在的问题-如果当UserPurchasedProduct事件发生时,产品甚至不在产品详细信息视图中,该怎么办


    为了避免任何并发问题,最简单的方法是让每个投影仅由单个进程和单个线程管理。这样,只要投影可以按顺序跨流接收事件(这样可以保证在产品购买之前看到产品创建),您就不会在产品存在之前看到购买。如果在投影中引入分片或任何其他多线程,它会变得更复杂。

    实际上,我就是这么想的。在我的解决方案中,我有一个“投影”,聆听购买和产品事件。但这个单一流程投影维护了两个mongo集合——用户购买和产品。目前,它还为不同的UI视图维护了更多的集合,但在需要时,可以轻松地将其拆分为更多的投影。尽管如此,这两个集合必须始终保持紧密并一起维护(同样在投影重建期间,所有事件都来自商店),基本上这是第一个选项,但添加了更多细节。