Event Sourcing 和 CQRS

目录

状态和事件

事件存储(Event Store)

事件朔源(Event Sourcing)

CQRS(Command Query Responsibility Segregation)

总结


状态和事件

我们可以把数据按照流的形式分为状态和事件,这个概念在大数据技术中被广泛地使用。状态是指一个数据的持久化表现形式,而事件是指达到某个状态的离散的过程。

比如张三在饭店点餐,点了一个汉堡、一包薯条、一杯可乐,在下单后,发现没有太大食欲,随后决定退掉一包薯条。基于上面的场景我们来尝试理解什么是状态和事件,以及它们之间的关系。

在上面的例子中,我们以订单的变更作为事件的触发,那么订单总共发生了两次改变。

  1. 下单:一个汉堡、一包薯条、一杯可乐
  2. 退单:一包薯条

我们把触发上面的两次改变的原因叫做事件,下单、退单是事件,而诸如“一包薯条”这样的数据是事件属性的一部分。

然后我们再来观察一下订单数据,订单数据在两次改变发生后分别变成了这样的数据

  1. 下单后:一个汉堡、一包薯条、一杯可乐
  2. 退单后:一个汉堡、一杯可乐

以上两个数据表现分别表达了下单时刻和退单时刻,订单数据的状态。事件和状态的关系如下图

本文主要讨论的内容聚焦于数据层面,所有这里的Action主要指将事件的数据属性聚合成为状态的过程,所有我们可以简单的将上面的过程描述成下图所示

 所以单从数据层面考虑,状态是由离散的事件通过聚合形成的。状态是持久化的,它具有一定的时效性,比如上面的例子,退单前后的状态就是其时效性的表现。当然,对于一个餐厅收费系统来讲,退单后的订单状态才是该系统需要关注的内容,因为张三只会支付“一个汉堡、一杯可乐”的钱,所以对于收费系统来讲,中间产生的下单后,退单前订单的状态显得可有可无。这也是我们大部分系统的传统做法(本文后面的内容我们也会使用传统一词来描述这种做法),这样的我们关注于结果而缺乏对于过程的了解。

商业运营者往往会在收益下降时,选择引入分析系统,而分析系统最主要的数据来源其实就是事件,而我们设计系统的传统做法,无法满足于分析系统对于事件的需求。可能你会说你的系统设计上使用了审计日志、数据库Delta工具(CDC)等等来保证类似于事件的存储,但是值得一提的是那样的设计在系统性能上是有消耗的,而且平心而论,这些设计不是面向对象的、也不是用例级别的,它只存在于事务级别,这一块我们后续聊到DDD的时候可以继续探讨。

总而言之,我们传统的做法是状态存储,状态存储可能会丢失一些具有商业价值的事件数据。而本文则主要是讨论基于事件存储的系统模型。

事件存储(Event Store)

事件存储即将发生的所有事件直接入库存储,对比状态存储的更新(Update)操作,事件存储则是追加(Append)操作。大数据技术栈中基于流的描述也有对于的更新流和事件流的描述,实属异曲同工。

下面是基于一条数据的状态存储(传统存储)和事件存储的对比图

 左边的圆圈是事件发生的时刻,和它平行的分别是状态存储(传统存储)和事件存储在该时刻需要入库的数据。我们可以看到,状态存储(传统存储)总是存储当前最新的数据状态,而事件存储则是以追加的形式将所有发生的事件入库。所以状态存储在任何时刻都只有一条数据,它使用更新操作来变更,而事件存储使用追加操作入库,不存在变更。而目前的存储(无论是数据库还是块存储)追加操作的性能远好于更新操作。

事件朔源(Event Sourcing)

上面讲到了事件存储,那么如果我们都只存储事件,那如何告诉我们的客户数据最新的状态呢?比如上面例子中的张三,应该付多少钱,付款金额取决于订单最终的状态。我们从本文的开头已经了解到了状态由事件聚合产生,而我们拥有所有的事件作为我们的数据源,剩下的就只是使用既定的算法将相关事件聚合起来形成状态。而从事件到状态的聚合过程就叫事件朔源,下图描述了所愿过程

 从上面的图可以看出,我们不仅可以通过聚合获得数据的最新状态,还可以通过设定时间窗口来获得历史上某个状态,这样不仅利于数据分析还有利于bug重现。另外现实情况是用户使用我们的系统,在大部分情况下应该是只关注于最新状态,那么为了节约聚合过程中的算力,我们可能会根据某种策略(比如基于时间或事件个数)以打版本的方式将过去的事件聚合成那个时刻的状态并将其当中数据事件入库(上图中Snapshot-1就是T0-T7聚合后的状态,如果时间窗口大于T7,我们只需要将Snapshot-1和T8的事件聚合即可得到最新状态)

CQRS(Command Query Responsibility Segregation)

上面打版本的方式可以弥补我们基于事件溯源的一些算力的浪费,但是现实情况是,可能大部分的用户操作都是基于对最新状态的查询。我们的所有业务都是基于此前提来构建的,比如付款需要订单的最新状态、云厂商提供的账单也是你最终使用的资源来计价的(它不会包含你中途选择了却没有创建的资源)。如此,我们基于事件存储和事件溯源的系统在查询性能上面肯定是落后于基于状态存储的系统的。

所以我们即希望于保留用户操作的所有事件,也希望拥有一个高性能的查询系统,一句话,我们要事件存储也要状态存储,这样我们的业务才完整。CQRS就应运而生,如下图(此图片来自于网络)

 我们的命令端使用事件存储,查询端使用状态存储,中间使用事件总线通信。这里新产生了一个概念:命令,命令是对象行为,是它产生了事件。

基于CQRS的系统结构,我们即保留的所有的事件数据,让我们的系统拥有了事件溯源的能力,又保留了状态存储的查询优势,二者兼得。

总结

基于商业目的,我们认为用户的任何操作都是有价值的,而基于审计日志、CDC等功能无法满足于我们的分析需求,至此基于事件的存储模型应运而生,而为兼容于查询需求我们将事件通过聚合形成状态,这让我们拥有了事件溯源的额外能力,除此以外我们使用打版本的操作来节约溯源的算里,但即便如此也无法满足我们高性能查询需求,于是提出了CQRS的系统结构。

后面的文章我们将继续介绍关于Event Sourcing和CQRS的落地技术

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值