事件溯源不是什么?

事件溯源开始被使用在高事务的环境(如证券交易所或赌博公司)。今天它被用于许多其他领域。我一直和很多人讨论这个架构筑风格,发现对事件溯源存在很常见的误解。

事件溯源术语是由Greg Young首先创建的,代表了一种以事件为中心的方法存储业务实体的架构模式。一个例子如电子商务应用程序,存储的是商品中基于属性上执行的不断更改的动作序列,而不是像传统方法一样存储商品的最终状态。

这使得我们能够灵活地进行时间的移动,执行时间查询,审计,故障排除等。它还使我们从行为方面更多地考虑到我们的应用程序,而不是静态结构方面。

事件这个术语和持久事件日志的存在会导致我们将事件溯源与消息传递之间发生关联,消息传递是在EDA(事件驱动架构)范围内应用的流行模式,我猜这是误解的起点。分布式系统通过事件异步执行所有的通信并不意味着它是事件溯源。

我们看看下面建立key-value存储的两个函数,使用bash程序:


#!/bin/bash

# storage functions
db_set () {
[[ $(grep "^$1," state_store) ]] && sed -i '' -e "s/^$1,.*/$1,$2/" state_store || echo $1,$2 >> state_store
}

db_get () {
grep
"^$1," state_store | sed -e "s/^$1,//"
}

# main flow
db_set name luise
db_set age 32
db_set name anna
db_get name
db_get age

这个脚本包含两个函数db_set和db_get,负责向文件存储或获得一对key/value值,db_get是根据指定key获得相应的值,db_set是在没有发现指定key则新增一对key/value其中,如果已经存在key,则修改对应key的value。

如果我们运行上述脚本,state_store文件将是:
name,anna
age,32

运行这个脚本输出如下:
name,anna
age,32

这个key-value中存储的是最新的key/value键值对,这种仅仅存储最新状态的方式有一些缺点,比如无法对如何导致当前状态的原因无法进行审计或故障排除。

让我们改一下上面的例子,我们可以这样做:


#!/bin/bash

# storage functions
db_set () {
echo "$1,$2" >> event_store
}

db_get () {
grep
"^$1," event_store | sed -e "s/^$1,//" | tail -n 1
}

# main flow
db_set name luise
db_set age 32
db_set name anna
db_get name
db_get age


上述示例是附加了一个非常简单的不可变事实/事件,表示“此条目已用此值更新”。

db_set函数现在只需将key/value键值对追加到event_store文件文件的末尾,而不是尝试更改现有状态。这样event_store文件保存的是所有更改动作集合列表。那么从这个event_store文件读取函数db_get就有点复杂了,因为我们希望仅显示返回最后一个状态,而不是所有更改动作列表。

现在event_store文件将是如下数据:
name,luise
age,32
name,anna

而运行上面新脚本的输出仍将与前面的示例相同。

这是一个太简单的案例,而且绝对不适合生产领域。示例中的事件模式是一对简单的键/值,允许我们拥有PUT语义。在现实情况下,我们还需要跟踪事件时间和事件模式版本。

但是这个案例仍然显示了事件溯源的有趣属性:

1.可审计性:检查过去的事件
2.高性能写入:向存储器追加是最易于写操作的。
3.可重放性:可以来回播放我的事件顺序
4.时间查询:是的,我们只需要添加该时间戳:)

现在我们总结一下事件溯源不是什么:

它不是顶级架构

顶级架构是指如何部署多个组件并相互通信。例如,这个架构可能是EDA(事件驱动架构)或SOA(面向服务的架构)。事件溯源则不是这样的。相反,事件溯源是一种应用程序架构模式,并且与任何其他设计模式一样,它通过一种良好的记录方法来解决特定的常见问题。

将事件溯源应用于整个系统实际上被认为是反模式。这是一种在内部创建事件溯源的大型单体monolith方法。


它不是框架
我们不构建事件溯源框架,我们不应为任何其他设计模式构建框架。

尽管如此,事件溯源意味着持久性,事件存储可以是一个有助于应用该模式的隔离系统,但它不是模式本身。常用存储系统的示例是Event Store和Apache Kafka。

它不是CQRS
事件溯源和CQRS(命令查询责任分离)已经被应用在一起几年了,但它们是不同的模式。

Bertrand Meyer在1988年的“面向对象软件构造”一书中,CQRS最初被记录为CQS(命令查询分离),并且从那时起已被应用了很多。

将状态建模为事件,很自然需要不同的读取方式,因为不能直接读取状态了,必须把事件转为状态。客户不想收到一连串的操作事件。相反,他们希望对他们期望的信息保持一致的状态。针对某些类型的查询或跨越多个有界的上下文,我们可以实现另外一种适合读取的存储方式(高速缓存,物化视图等)。

它不是异步或最终一致
它与最终的一致性无关。前面上述脚本正在存储事件,每个读取可以正在抓取所有事件以显示最新状态。

我们也可以异步地做这些,我可以通过后台任务读取事件和更新其他地方的状态,并改变读取路径。这将使其最终保持一致。但是我们不必这样做。

世界上最流行的事件溯源应用是Git。Git是完全同步的,并且具有作为事件溯源的所有优点,例如在时间上进行时间查询或及时来回地在不同的时间点中挑选代码以及其他用例。

它不存储发生的一切

除非事件非常少量或有足够的存储空间,否则状态并不是由每一个曾经存在的事件构成的。与事件溯源相关的是执行快照或压缩的概念,从什么时候开始播放事件变得很重要,因此重视“时间的开始”更为现实。

从此得出结论?

事件溯源通常被错误地与EDA(事件驱动架构)相关联。事件溯源是一种应用程序架构模式,它允许更好的审计,提供状态重播或迁移动到特定时间点的能力,它自然地提高了写方面的性能改进,因为将事件追加(不是修改)到持久存储中真的很快。

采集整个系统的事件是一个很大的错误,被认为是一种反模式。它本身不是架构,它不会使应用程序更好地通信,它只是通过存储发生的事实而不是存储当前视图,使单个应用程序更加一致和可审计。

这很像当我丢失了车钥匙,只能在我的头脑中重播所有刚发生的事情,并且我同步地和一致地做这些事情,直到我再次找到它,否则我需要乘坐公共汽车。


What Event Sourcing is not