Event Sourcing是微服务中的一种架构模式,其背后的想法非常简单和优雅:把系统中发生的事件都记录下来,在任何时候,通过回放历史事件,我们就能得到系统的最新状态,此外,如果我们想回看系统在过去某个时刻的状态,通过回放历史事件也可以轻松做到。
Event Sourcing给我们提供了如下好处:
对审计日志的天然支持
大大简化了业务的写操作
避免了琐碎的O/R mapping
为系统提供了一个时间机,可以让系统回到历史的任意时刻
很多介绍Event Sourcing的文章往往集中在对其优势的讲解,对其面对的挑战却语焉不详,这里列出几个实战中需要考虑的问题,供大家思考。
世界观错配
在Event Sourcing的世界中,系统是由一系列的事件,以及叠加在这些事件上业务规则组成的。而在业务的世界,系统是由一系列业务实体,以及这些实体间进行协作的业务规则组成的。一方面我们需要为Event Sourcing设计一套事件存储和处理机制,另一方面我们依然需要设计大量的业务实体、事务边界等,之后我们还需要一个映射机制把这两个世界连接起来,这种错配是我们系统复杂性的一个根本来源。
处理Schema的变更
在开发过程中,数据结构的变化是很常见的。当Event的结构发生变化时,面对我们在Event Store里存储的亿万条历史事件,我们该如何处理?
Event Sourcing有一个基本理念就是历史是不可更改的,如果我们严格遵循这个理念,每次Event结构发生变化的时候,我们就不得不在代码中开辟一个分支,很快我们的代码将变得丑陋和难以维护。
如果想保持代码的可维护性,我们还有一个选择,就是每当Event发生结构改变时,就把历史上所有的事件按照新的结构更新一遍,然后再把所有的Snapshot也重新计算一遍,这不但非常耗时耗力,而且如果这个结构变更不是增量式的,而是修改或者删除了某些信息,那么我们将不可避免地更改历史,Event Sourcing将不再能起到时间机的作用。
处理数据的可见性
在Event Sourcing模式下,我们只在系统中存储了原始的事件,我们并不存储系统的状态,这就导致Event Sourcing对Query非常不友好。
对于简单的Query,我们可以先通过实时回放历史事件来得到系统状态,然后返回查询结果。对于复杂的Query,我们将不得不建立各种视图,配合CQRS模式来解决问题,这不但增加了系统的复杂性(建立哪些视图,何时更新视图),而且,视图代表的是系统状态,随着我们建立的视图越来越多,我们越来越走到了Event Sourcing基本理念的反面。
另外有时候,有些bug导致系统处在不正确的状态上,在传统模式下,这类bug非常容易修复,而在Event Sourcing模式下,这类bug不但难以定位,而且修复方案也往往很复杂。
Snapshot的管理
如果一个系统已经存储了很多的历史事件,为了获得系统的当前状态,每次都从头回放历史将会非常低效,因此,我们可以创建一些历史的Snapshot,之后就可以从这些Snapshot来进行回放以提高效率了。
但一个系统的不同部分,其事件的发生频率常常有数量级的差异,回放不同事件的复杂度也差异巨大,对系统不同部分当前状态的查询方式也常常各不相同。这就带来了在什么时间、如何创建Snapshot的问题,这往往没有一个简单解决方案,实践中的解决方案通常都比较复杂。
学习曲线和解释成本
Event Sourcing是一个相对抽象的模式,常见的情况是,每当团队有新人加入的时候,他们总要花很多时间才能理解这个模式,再花更多时间掌握那些在实战中要注意的环节。另外,由于在Event Sourcing模式下,只有历史事件没有系统状态,所以在很多实际问题的分析中,往往需要更多的解释成本才能说清楚问题。在追求快速迭代的团队中,时间就是最大的成本。
Event Sourcing有其自身的特点和优势,但是我们在决定是否要采用这一模式的时候,也需要考虑可能要面对的挑战,适合自己的架构才是最好的架构。