从“快照”投影而不是事件存储中补水聚合


14

因此,尽管我从未有机会在实际项目中应用这些模式,但我一直在调动Event Sourcing和CQRS一段时间。

我了解将读取和写入问题分开的好处,并且感谢事件搜寻如何使将状态更改轻松地投影到与事件存储区不同的“读取模型”数据库中变得容易。

我不清楚的是,为什么您会从Event Store自身中重新添加聚合。

如果将更改投影到“读取”数据库很容易,为什么不总是将更改投影到其架构与您的域模型完全匹配的“写入”数据库呢?这实际上将是快照数据库。

我想这在野外的ES + CQRS应用程序中一定很常见。

在这种情况下,事件存储仅在由于架构更改而重建“写”数据库时有用吗?还是我想念更大的东西?


异步写入写模型状态存储并专门使用它来加载实体没有错。无论您是否执行相同的精确一致性问题。解决这些一致性问题的关键是对实体进行不同的建模。解决这些一致性问题的事件来源没有什么神奇之处。魔术在于建模而不是关怀。有些特定的应用程序确实需要在那个级别保持一致性,这些应用程序无论您如何建模都具有高度争议的实体,无论如何都需要特别注意。
安德鲁·拉尔森

只要您可以保证事件的交付。为此,您的应用程序仅需要将事件同步发布到持久事件总线上。发布后,应用程序的工作完成。然后,总线会将其传递给各种事件处理程序:一个用于更新事件存储,一个用于更新状态存储,以及其他任何需要更新读取存储的事件处理程序。使用事件源的原因是因为您不再关心即时一致性。接受它。
安德鲁·拉尔森

没有理由应该从事件存储中不断加载实体。那不是目的。其目的是为系统中发生的所有事情提供原始的永久分类帐。实体状态存储和非规范化的读取模型用于加载和读取。
安德鲁·拉尔森

Answers:


14

我不清楚的是,为什么您会从Event Store自身中重新添加聚合。

因为“事件”是记录。

如果将更改投影到“读取”数据库很容易,为什么不总是将更改投影到其架构与您的域模型完全匹配的“写入”数据库呢?这实际上将是快照数据库。

是; 使用聚合状态的缓存副本,而不是每次都从头重新生成该状态,有时是一种有用的性能优化。请记住:性能优化的第一条规则是“不要”。它给解决方案增加了额外的复杂性,您最好避免这种情况,除非您有令人信服的商业动机。

在这种情况下,事件存储仅在由于架构更改而重建“写”数据库时有用吗?还是我想念更大的东西?

您缺少更大的东西。

第一点是,如果您正在考虑基于事件的解决方案,那是因为您希望保留发生的事件的历史是有价值的,这就是说您要进行无损更改

因此,这就是我们要写入事件存储区的原因。

特别是,这意味着每个更改都需要写入事件存储。

如果彼此竞争的作家不知道彼此的编辑,它们可能会破坏彼此的写作,或将系统驱动到意外状态。因此,当您需要一致性时,通常的方法是将您的写入寻址到日志中的特定位置(类似于HTTP api中的条件PUT)。写入失败会告诉作者,他们对期刊的当前理解不同步,应该恢复。

返回已知的良好位置,然后重播任何其他事件,因为这是常见的恢复策略。已知的良好位置可以是本地高速缓存中内容的副本,也可以是快照存储中的表示形式。

在快乐路径中,您可以在内存中保留聚合的快照;您只需要在没有可用的本地副本时联系外部商店。

此外,如果您可以访问记录簿,则无需完全追赶。

因此,通常的方法(如果使用快照存储库)是异步维护它。这样,如果您确实需要恢复,则无需重新加载和重放聚合的整个历史记录即可。

在许多情况下,这种复杂性并不令人关注,因为具有范围范围的生存期的细粒度聚合通常不会收集足够的事件,其好处无法超过维护快照缓存的成本。

但是,当它是解决问题的正确工具时,将聚合的陈旧表示加载到写入模型中,然后使用其他事件对其进行更新是一件完全合理的事情。


这听起来很合理。前一段时间,当我尝试使用ES的虚拟实现时,我使用EventStore来保证一致性,但同时也同步写入了您所谓的“快照存储库”。这意味着当前状态始终可以随时读取而不必重播事件。我怀疑这不能很好地扩展,但是由于这只是一种练习,所以我不介意。
MetaFight

6

由于您未指定“写入”数据库的用途,因此在此我要假设的意思是:将新更新注册到聚合中时,您无需从事件存储中重建聚合,而是从“写入”数据库中删除它,验证更改并发出事件。

如果这是您的意思,那么该策略将为不一致创造条件:如果在最后一个机会将其放入“写入”数据库之前进行了新的更新,则该新更新将针对过时的数据进行验证,因此有可能发出“不可能”(即“不允许”)事件并破坏系统状态。

例如,考虑在剧院预订座位的典型例子。为防止重复预订,您需要确保预订的座位尚未被占用-这就是您所说的“验证”。为此,您将已预订的座位列表存储在“写”数据库中。然后,当收到预订请求时,您检查所请求的座位是否在列表中,如果不是,则发出“已预订”事件,否则返回错误消息。然后,您运行一个投影过程,在此侦听“已预订”事件并将已预订座位添加到“写入”数据库的列表中。

通常,系统的功能如下:

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Projection process catches the event, adds seat #1 to the "already booked" list.
5. Another request to book seat #1.
6. Check in the list: the list contains seat #1
7. Respond with an error message.

但是,如果请求传入的速度太快,而步骤5在步骤4之前发生,该怎么办?

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Another request to book seat #1.
5. Check in the list: the list is still empty.
6. Issue another "booked seat #1" event.

现在,您有两个事件可预订同一座位。系统状态已损坏。

为防止这种情况发生,您永远不要针对投影验证更新。要验证更新,请从事件存储区重建聚合,然后根据事件验证更新。之后,您发出一个事件,但是使用时间戳保护以确保自从您上次从商店读取以来没有发出新的事件。如果失败,则重试。

从事件存储中重建聚合可能会降低性能。为了减轻这种情况,您可以将聚合快照直接存储在事件流中,并用创建快照的事件ID进行标记。这样,您可以通过加载最新快照并仅重播快照之后的事件来重建聚合,而不是始终从一开始就重播整个事件流。


感谢您的回答(也很抱歉花了这么长时间回复)。您说的针对写数据库进行验证的说法不一定正确。正如我在另一条评论中提到的那样,在示例ES实现中,我确保同步更新写数据库(并存储concurrencyId / timestamp)。这使我能够检测到乐观并发冲突,而无需从EventStore准备就绪。当然,仅同步写入并不能防止数据损坏,但是我也在做单访问(单线程)写入。
MetaFight

因此,我解决了我的一致性问题。但是,我认为这是以可伸缩性为代价的。
MetaFight

同步写入写入数据库仍然存在损坏的危险:如果对事件存储的写入成功,但是对写入数据库的写入失败,会发生什么呢?
Fyodor Soikin,

1
如果读取投影失败,它将重试直到成功。如果它完全崩溃,它将唤醒并从崩溃的地方继续-换句话说,也请重试。外部可观察到的效果与运行速度稍有不同。如果预测不断失败并持续失败,则意味着其中存在错误,必须对此进行修复。修复后,它将从上一个良好状态恢复运行。如果整个读取的数据库由于错误而损坏,我将使用事件历史记录从头开始重建数据库。
Fyodor Soikin

1
没有数据丢失,这是重点。数据可能会暂时停留在不方便的形状(用于读取)中,但是它永远不会丢失。
Fyodor Soikin

3

主要原因是性能。您可以为每个提交存储一个快照(commit =单个命令生成的事件,通常只有一个事件),但这很昂贵。沿着快照,您还需要存储提交,否则将不是事件源。所有这些必须原子地完成,全部或全部不做。仅当使用单独的数据库/表/集合时,您的问题才有效(否则将完全是事件源),因此为了确保一致性,您不得不使用事务。交易不可扩展。仅追加事件流(事件存储)是可伸缩性的源泉。

第二个原因是聚合封装。您需要保护它。这意味着聚合应该随时可以自由更改其内部表示。如果存储它并高度依赖它,那么版本控制将非常困难,这肯定会发生。在您仅将快照用作优化的情况下,当架构更改时,您只需忽略那些快照(简单地说?我并不这么认为;祝您好运,确定Aggregate的架构更改-包括所有嵌套实体和值对象)有效的方法并对其进行管理)。


当我的聚合架构更改时,重播事件以生成更新的“写”数据库不是一个简单的问题吗?
MetaFight

问题在于检测到这种变化。聚合可能非常大,包含许多文件/类。
康斯坦丁·加尔贝努

我不明白 更改将在软件版本中发生。该版本可能附带数据库脚本以重新生成“写入”数据库。
MetaFight

迁移脚本需要做很多工作。在运行时,应用程序必须关闭。
康斯坦丁·加尔贝努

@MetaFight如果流很大,则将需要很多时间来重建新的聚合模式...我现在正在考虑快照,该快照是实时投影的状态,可以在发布新的聚合之前运行模式
-Narvalex
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.