如何在CQRS+事件溯源架构中管理ViewModel的更改

23
我们目前正在评估CQRS和事件溯源架构。我试图理解使用这种设计的维护影响。我正在努力找到答案的两个问题是:
1)如果在应用程序运行一段时间后,有一个新的要求需要向ReadModel数据库中的ViewModel添加一个额外的字段呢?例如,在以前未考虑的情况下,客户邮政编码在CustomerList ViewModel上是必需的。因此,可以轻松地将额外的列添加到ViewModel数据库中,但是如何填充它?就我所见,唯一的方法是清除read database,并从头开始重放所有事件以重新构建ReadModel Database。但是,如果应用程序已经运行了几个月或几年(我们希望它能够),那么这可能是要重放数百万个事件,仅仅为了添加一个Zipcode列的数据。
如果由于某种技术原因,ReadModel Database不同步,或者我们想添加一个新的ReadModel database,我有相同的担忧。似乎应用程序越老,使用越多,就越难、越昂贵地获取最新的readmodel。或者我错过了什么技巧?类似于ReadModel快照之类的东西吗?
2)如果重放几百万个事件以重新构建read database后,发现一些数据与预期不符(即,它看起来有错误)。认为可能是事件存储或去规范化例程中存在某个错误(如果有什么可以依赖编码的东西,那就是错误)。如何调试这个问题!它似乎是一个不可能的任务。或者,也许我又错过了什么技巧。
我很想听听任何长期运行此类系统的人,他们的维护和升级路径是如何工作的。
感谢您花费时间和参与。
4个回答

16
使用事件溯源与CQRS的美妙之处在于能够销毁读模型并从头开始重建,正如先前所述。由于某种原因,人们认为,在事件数超过某个任意值后,需要花费很长时间才能完成。如果您正在使用关系数据库来构建读模型 - 并且您很可能正在使用 - 它很容易打开一个事务,通过处理程序阅读所有事件,然后提交事务。只有在事务提交时,我们才会实际接触磁盘。其余所有操作都是在内存中进行,因此速度非常快。实际上,如果这样做,我不会感到惊讶,你的系统在几分钟内就可以处理几百万个事件。
从头开始重建您的读模型应该显示为完全相同的方式,就像将事件规范化为读模型的日常方法一样。如果没有,说明在读模型规范化代码中存在错误。这里的重要之处在于,从消息处理程序的角度看,接收到的事件被规范化为读模型以供生产场景和读模型重建场景使用没有区别。
如果遇到错误,您可以通过将生产事件流式复制到本地工作站,设置处理程序断点,然后运行那些事件继续处理读模型代码来轻松调试。

感谢您的回复。您对事件重放时间不是问题(即使有数百万个事件)的看法令人放心。顺便说一下,我很喜欢您的博客,感谢您的分享! - James
1
你肯定需要进行一些测试,因为如果做得不好,视图模型的填充可能会很慢。要使其快速,需要付出一点努力。 - Jonathan Oliver
我们一直在实践每次推向生产环境时重建我们的读模型。我们首先重建数据并将其推送到我们的阶段读模型,如果重建成功,我们将进行重建并将其推送到我们的生产读模型。对我们来说,这确保了读模型反映了每个版本中事件处理程序的所有修改。 - Brett Allred
使用事件溯源和CQRS的美妙之处在于能够摧毁读模型并从头开始重建,正如先前所提到的。采用CQRS风格的奇怪原因是能够销毁数据库... - Alex Burtsev
1
尽管您正在删除一个可能包含事件投影的数据库,但您不会丢失任何数据。相反,通常只有在业务需求发生变化并且不再需要特定的视图时,才会删除数据库。然后,您可以使用新的处理程序重新播放事件,根据当前的业务需求创建不同的视图/投影 - 而无需真正丢失数据。 - Jonathan Oliver
@JonathanOliver 关于重建,建议在读取端从写入端复制事件流吗?目前我只是从写入端引发的事件更新我的读取模型,但是,如果我需要删除我的读取数据库,我该如何重建?看起来不必要地将所有事件从域侧再次传递到读取侧似乎是不必要的工作,对我来说,在读取侧事件流中缓存这些事件是最优化的方法?不确定是否存在任何陷阱或者您能否想到更好的方法。 - James

2
我对CQRS有些陌生,所以这可能不是最明智的方法(但如果我没记错的话,我是从CQRS/DDDD邮件列表中学到的)。
我们创建一个特定于预期运行一次然后被弃用的命令和相应的处理程序。
在处理程序中,我们使用方便的机制,因此在您添加邮政编码字段的情况下,我们可以运行一个一次性查询,从另一个视图模型中提取此时的邮政编码并填充新列。在这些情况下,我们不太担心架构纯度,因为它预计只是一次操作(Rob Conery的 Massive 在这些情况下已经成功地使用过)。

是的,一个将ES/CQRS架构投入生产的工程师告诉我,他们通常将数据模型更改视为常规事件,并尽力管理它们。特别是当读取端需要处理的是属性取消而不是添加时,情况会变得非常棘手,因为在这种情况下,你只需要更新你的代码(当强类型时),并提供默认值。 - Carmine Ingaldi

1

我还没有使用cqrs和事件溯源构建生产就绪的应用程序,所以这里只是我的尝试构建一个的经验。

1)读模型重建。是的,一旦其中的内容发生变化,您基本上必须重新构建整个读模型数据库。如果有很多事件,这可能需要很长时间。因此,读模型重建必须高度优化(使用事件批处理等)。我觉得事件溯源最适合高读写比例的情况。因此,对于某些极其不稳定的数据,可能明智的做法是不将其存储为域事件。但是,关于存储容量的问题也不远了。无论如何,您可以将cqrs应用于系统的一部分,即最适合它的部分(例如,我可能不会将图形图像存储为事件的一部分)。

2) 调试。 事件存储中出现错误的可能性极小(这应该是框架的责任),并且检查存储中有哪些事件始终很容易。至于生成预期事件的命令,您应该在此处编写测试,这些测试可能是系统中最有价值的测试。对于反规范化器,您也可以编写测试,但如果可以通过肉眼看到其正确性,则不必为琐碎的反规范化器编写测试。话虽如此,我曾经使用调试器几次来查找一些更复杂的反规范化器中的问题;尝试确定哪个事件导致问题出错并不是那么有趣。


0

在您的模型中,添加一个净额事件也是可能的。这可以在接收到 X 个事件(比如 500)之后作为任意任务运行。

要重新构建,您将事件推入堆栈,直到遇到净额事件,这将被用作基线,从此处弹出堆栈中的事件并与基线事件聚合其值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接