作者 | 苏晓风
我们经常看到随着Event Sourcing一起出现的,还有几个大家比较熟知的概念:CQRS, EDA(Event-driven Architecture),当然还有DDD。在经历过采用Event Sourcing的项目后,我想和大家讨论一下,当我们提到Event Sourcing时,我们在说什么?再简单阐述一下这四个概念之间的关系。
Event Sourcing的概念
提到Event Sourcing,我们会联想到一个非常相近的生活中的例子就是会计账本,会计账簿上的会计条目按照发生的时间顺序,记录了对账户余额产生变更的事件。通过会计账簿的记录,我们可以计算出任意时间点的账户余额。如果说有一类应用程序需要留存对最终结果造成改变的所有事件,那Event Sourcing就像是这类应用程序的概念抽象。所以我们看到Event Sourcing也有着和会计账簿一样的特征:
- 不是保留当前状态,而是保留所有导致状态改变的事件
- 事件会按发生的时间顺序被记录下来
- 通过事件重建得到状态
听起来Event Sourcing的概念也没有那么难理解,更加贴合现实,还保存了所有真实事件,如果有审计相关的需求,显然是很容易得到审计需要的数据。
Event Sourcing在Node.js里并不是一个被广泛使用的成熟设计,我们很难在市面上找到成熟的Node.js的Event Sourcing框架,这意味这我们可能会面临更多的未预知的问题。除了未预知的问题之外,开发团队还面临着巨大的思维转变:系统中的一等公民变成了事件,所有的逻辑都是围绕着事件展开,系统状态不再是一个一定需要被持久化的元素了。这听起来很简单但是实践起来却并非只言两语可概括般的容易。
在什么情况下需要用到Event Sourcing?
我了解到有的项目有基于命令转化为事件,并将事件持久化到数据库,但是在此同时他们也把command转化为snapshot保存了下来。读模型的构建全部基于snapshot。该团队确实将系统发生的真实事件全部留存了下来,但实际并没有通过事件重建得到状态,所有的状态都是来自于另外处理得到的snapshot。严格上来说并不能算做是使用了Event Sourcing,系统中做到了留存Event,但是并没有用到Sourcing。
基于一些背景信息,当时该项目使用Event Sourcing的出发点在于,客户强烈要求将DDD的思想和产出的模型完全代码化,特别是在Event Storming过程中的产出。
上面的例子不禁让我们思考一个问题:究竟在什么情况下需要用到Event Soucing?
为了回答这个问题我们先来看看Event Sourcing中的核心概念:
- Event:发生的事实,也是唯一真实的数据来源。用过去式来表述。系统中的事件是immutable的,不能被更改和删除,只能通过添加新的事件来改变当前的系统状态。
- 完全重建(Rebuild):我们可以完全丢弃系统状态,在任何时间通过事件日志按照时间顺序重演出当前系统状态。
- 事件回放(Replay): 就像平时浏览视频一样,如果视频总时长是半小时,我们想回到25分,我们可以直接把进度条向后拉到25分。在Event Soucring的系统里,我们可以基于某个重建出来的系统状态,回放后续的事件,得到我们想要的某个时间节点的系统状态。
又比如: 我们的会计账簿里保存了过去一年全部的会计条目,现在想要得到5月30号当天的账户余额,在此前因为业务要求我们已经得到了每个季度结束的账户余额。那我们可以通过已知的5月31号的账户余额对5月31号发生的所有存款和取款进行反向的重放得到5月30号的余额。
使用Event Sourcing的好处
基于Event Sourcing的特性,我们可以来探讨下它究竟能给我们的系统或者说业务带来怎样的好处?
- 审计追踪:首先它留存所有真实事件的设计天然地为后期审计追踪提供了便利,因为系统里留存下了所有现实中产生的痕迹,并且这些痕迹都不被允许修改;
- 适应多样的查询需求:我们的系统状态都是来自于事件,那意味着我们可以根据不同的查询需求构建出不同的读模型,以适应业务需求。这也是为什么我们看到Event Sourcing会经常伴随CQRS出现的一个原因。因为在Event Sourcing的系统里我们可以利用其特性,分离读写模型;
- 调试:这个优点的来源同样是保存了所有的事件,这意味着当我们线上环境出问题时,我们可以把线上环境的所有event拿到一个类线上环境下测试, 找到问题出在哪儿;
- 可以得到系统任何时间点的状态;
- 系统状态可以是内存内的,不一定要持久化到数据库:任何事情发生时,就像服务崩溃的时候,我们都可以通过事件重建得到系统状态。这样你就不需要考虑持久化到数据库会涉及到的各种Data Mapping的逻辑了;
- 领域事件是有价值的,存下产生的领域事件,不丢失所有的现实痕迹,为支撑后期业务扩展,提供商业数据分析的数据源。
从它能带来的优点来看,当我们的业务需求有:
- 能够保留下所有的事件以适应审计的需求;
- 客户认为系统中发生的事实都是很有价值的,一定要保存下来,以便支撑后续业务扩张的商务分析;
- 需要经常查询不固定时间点的系统状态;
- 多种多样的基于不同维度的查询需求时,不妨考虑一下Event Sourcing。
当然决定用它之前我们还是得考虑一下它的缺点:
- 事件的版本: 对于不同类型或者不同聚合根下的事件我们有着不一样的Event Handler, 而当业务演进的过程中,相应地对事件的处理也会不同。这意味着我们在业务扩展的时候需要考虑兼容旧的事件;
- 业务发生改变后,为适应业务需求我们需要replay出的application state也会可能发生改变,那我们要如何兼容旧的事件rebuild或replay出新结构的application state?
- 让开发团队感到陌生的设计思想;
- 较少成熟的Event Sourcing的框架支持;
- 在Event store中需要序列化Event。
Event Sourcing和其他架构之间的关系
回到文章开头提到的四个经常被拿来一起说的概念:当我们决定使用Event Sourcing作为架构选择之时,通常我们也会选择DDD去构建得到领域事件。DDD里提到的Event指的是对系统状态产生改变的现实事件,同样我们在Event Sourcing的系统中存储的也是会导致系统状态改变的事件。似乎这两种不同的软件开发思想,对Event的认识有着不谋而合的默契。
用到Event Sourcing的系统又绝大部分都会采用CQRS。因为我们持久化的event和查询所需的结构很显然是有区别的,与其每次查询都通过Rebuild或者Replay去得到查询所需的状态,我们一般都会根据查询需求构建出查询需要且方便的读模型。即便如此,当我们决定选用CQRS时,还是得考虑引入后会增加的的复杂度。这也意味着不是所有的Event Sourcing的系统都需要采用CQRS。
至于EDA那其实是完全没有太大关系的概念了,不过我们经常在处理服务之间通信的问题的时候会用到。当我们的项目恰好是微服务,又采用了DDD,还加上Event Sourcing和CQRS那我们还需要引入EDA的时候,就要小心我们平时的技术讨论中一定要分清楚我们所说的Event是在怎样的上下文下的。想要更多的了解EDA的概念可以参看Martin Fowler“当提到“事件驱动”时,我们在说什么?”的文章,其中也提到了我们经常会混用Event Sourcing,EDA,CQRS中的一些概念。
希望这篇文章能够引发你对Event Sourcing的设计思想的一些思考。也期望后续我还能再完成一篇Event Sourcing实战的文章。这篇文章其实还是有些遗漏的地方,比如在Event Sourcing架构选择决策的缺点部分,但是考虑到实际选用Event Sourcing架构的情况下通常还会选用其他的设计以及架构,比如文中反复提到的CQRS和DDD,在最终决定的架构下也会引入除了本文所提的缺点之外的其他问题,但因为我认为这并不算是Event Sourcing架构本身带来的问题故没有在文中深究。但是如果大家真的决定选用Event Sourcing作为系统设计思想的一部分的话还是需要对Event Sourcing的应用做更多的探索,本文还是旨在阐明Event Sourcing的概念,消除大家对于Event Sourcing的部分误解。