事件来源和CQRS,我错过了什么?

问题描述 投票:1回答:4

我开始阅读结合CQRS的事件源模式。据我了解,CQRS模式是一种将写和读动作分开的模式。事件源是一种模式,其中系统中的所有内容均由触发事件的命令启动。事件源模式需要事件总线。有些事情我没能理解。

事件存储包含某个实体发生的所有事件。如果要查询该实体的当前状态,则需要查询该实体发生的所有事件,然后重新创建它的当前状态。所有事件历史记录都存在于事件存储中。为什么我不能拥有负责将每个事件保存到事件数据库的微服务(如果我想记录这些事件以进行进一步的操作,例如kafka)和单独的微服务来定期更新实体上的更改数据库(例如,对mongodb中实体文档的简单更新)。这些微服务完成工作后,将从事件存储中删除此事件(假设我使用队列来实现此事件存储)。这样,无论何时需要查询实体的当前状态,我都只需查询数据库,而不是查询事件存储并重建当前状态(或基于事件存储重新计算状态并定期缓存结果)。我不明白为什么永久存储所有事件是强制性的,为什么它不是可选的?

例如,接收事件,生成事件并将其存储在每种事件类型的单独SQS中的Lambda函数。每个SQS都有自己的lambda函数,负责处理相应的事件类型。该事件将被删除并进行处理。

design-patterns cqrs event-sourcing
4个回答
1
投票

事件源(带有或不带有CQRS)特别是指通常使用特定于域的事件来存储实体的状态。当需要运行需要来自该实体的数据的业务逻辑时,可以将事件按顺序投影到状态上并使用它。

将域事件存储在像Kafka这样的东西中,但是将实体本身存储(通过将事件投影到然后存储该事件或其他任何东西)到文档或常规形式的db中,这是绝对有效的,事件源。

[我假设您知道事件源的好处,所以在这里我不会赘述,但是随时添加评论,我会在上面进行扩展。

为什么不将事件存储在像Kafka这样的东西中,并且在加载真正的事件源期间不使用它们?如果您不将快照与事件存储在同一数据库中,那么您将冒着出现并发冲突的真正风险:例如,两次进入,引发冲突的事件,或者如果您决定使用最多-则缺少事件,关于引发事件的语义。这些直接意味着您无法真正依靠自己发出的事件成为真理的来源。


0
投票

我不明白为什么必须永久存储所有事件,为什么它不是可选的?

Martin Fowler,(强调我的意思)

我们可以查询应用程序的状态以了解世界的当前状态,这回答了许多问题。但是,有时候我们不只是想看看自己在哪里,我们也想知道我们如何到达那里

这导致可以在事件日志的顶部构建许多功能:

  • 完全重建:我们可以完全放弃应用程序状态并重建它...
  • 临时查询:我们可以在任何时间确定应用程序状态...
  • 事件重播:如果过去的事件不正确,我们可以通过反转事件来计算后果...

之所以不能丢弃事件,是因为事件本身具有价值。如果不是这种情况,那么“事件源”由于many tradeoffs而不是一个不错的选择。事件源模式要求保存所有事件,以便可以重复使用。


0
投票

事件源模式需要事件总线。

事件搜索不需要总线,除非您需要将更改(事件)通知其他系统/域。

如果我想查询该实体的当前状态,我需要查询该实体发生的所有事件,并重新创建它的当前状态。

嗯,有点。在处理新命令时,您只需要[[need即可执行此操作,并且需要验证应用该命令不会使“实体”(如您所说的)不一致。请注意,这涉及CQRS的command端,而不是查询端。

对于查询/读取模型方面,您有很多不同的选择。使用事件源来拥有一个单独的数据存储来维护事件的非规范化版本以及随着事件发生而更新的相关数据是很常见的。这个单独的存储通常为Eventually Consistent,对于此答案而言,太多了。您的读取模型还可以是关系数据库,平面文件,或者实际上是您可以想到的其他任何存储数据的方式。通过通过总线接收事件,轮询数据库或其他方式来接收事件的发生,从而使其数据与写入模型保持一致。

查询事件流并实时处理(或部分处理)事件以构造查询也是绝对有效的,但是这种情况很少见。

所有事件历史记录都存在于事件存储中。我为什么不能负责将每个事件保存到一个微服务的微服务事件数据库(如果我想记录这些事件以进行进一步的操作。像kafka)和单独的微服务,用于更新对常规数据库中实体的更改(对实体的简单更新例如mongodb中的文档)。

您可以!

[这些微服务完成工作后,从事件存储中删除(假设我实现了此事件存储使用队列)。

您也可以执行此操作,但是您就没有在进行事件源。这更像是“事件驱动的体系结构”,它在没有使用事件源的情况下是可能且完全有效的,但是并不能提供所有相同的好处。在基于事件的系统中,事件存储是数据真相的来源,并且队列不是存储真相的有效位置,因为它并不是要长期存储数据。

[当您执行CQRS时,尤其是在进行事件搜索时,您需要更改“当前状态”含义的思维模型。实际真相存储在某个地方(事件存储,关系数据库等),当您查询时,将该真相投影为所需的任何格式。

例如,我有一个用户数据库,该数据库在一个列中存储名字,在另一列中存储姓氏。代表我的行在“名字”列中包含“ Phil”,在“姓氏”列中具有“ Sandler”。在UI中显示数据时,将其显示为“ Sandler,Phil”。为什么不将其作为“ Sandler,Phil”存储在文档数据库中并完成它呢?因为通过标准化数据,我已经准确记录了事实,并可以选择在将来有需要时对数据进行不同的投影。

上面的示例中的当前状态是存储在两列中的数据,还是“ Sandler,Phil”?在CQRS中,您不应该从当前状态来考虑它,而应该在两个单独的模型中来考虑真相(写面)和如何投影(读面)。


0
投票
如上所述,事件源不需要总线,它需要事件存储。

您所引用的模式(读取所有事件以重新构成实体状态)就是我所谓的事件源的“域驱动设计风格”。

您的想法与面向“事件+状态”的方法更多相关。

让我们看一下这两种方法。

DDD和聚合流

DDD战术模式之一是汇总模式。它基本上是一个一致性边界。命令只能应用于单个聚合实例,因此形成事务。处理命令时,聚合状态会更改,因此会产生新的域事件(或多个事件)。然后,我们将事件作为一个事务存储在事件存储中。单个实体的所有事件都存储在一个流中,我们通常将其称为“聚合流”,并且流名称通常由聚合类型及其ID(例如Order-123)组成。

这里的目的是聚合的意义-一致性。绝对确定要对聚合的最新状态执行命令的唯一方法是读取所有事件(或快照以及快照之后的所有事件)。

我不确定您提到“查询实体状态”时的意思。如果您的意思是“通过id获取实体状态”,那似乎是正确的。对于查询,您无需这样做。这就是CQRS发挥作用的地方。您将必要的事件投影到另一个可以运行查询的数据库位置。在该数据库中,您具有实体的预计状态。投影仅使用一种实体类型的事件是没有限制的,实际上它实际上是一种反模式。读取模型(计划状态)用于特定目的,通常由用户的需求(各种UI)驱动。

事件+状态

有很多事件源系统可以完全按照您的描述进行操作-将实体状态投影到另一个存储中,因此您始终拥有一个现成的,易于访问的实体状态,而无需从头到尾读取事件再来一次。

听起来很吸引人,但是您必须确保写入事件和更新此快照是事务性的。在您描述的体系结构中,当您具有将事件投影到文档数据库的功能时,它将无法使用。实体状态快照将始终保持最终一致性。因此,当您执行命令时,您很容易遇到这种情况,该命令在陈旧的实体快照上运行,因此向系统引入了一些奇怪的行为。最糟糕的是,所有测试都会变得贪婪,并且会在系统加载后在生产中发生。这样的错误是令人讨厌的,很难捕获。

关于其他事情,我相信其他答案已经涵盖了这些要点。

© www.soinside.com 2019 - 2024. All rights reserved.