2020 Google开发者大会重磅开幕 了解详情

如何理解事件溯源

2017 年 9 月 07 日

在近期举行的 PHPDublin 见面会上,来自 DynamicRes 的架构师 Barry Sullivan 被问到“什么是事件溯源”,作为对这个问题的回答,他在博客上写下了这篇文章,详细解释了什么是事件溯源以及事件溯源有哪些好处。以下内容翻译自 Barry 的博客,已获得作者授权。

Web 开发的现状

在详细解释事件溯源之前,先让我们来看看 Web 开发的现状。

当前的 Web 开发是以数据库作为驱动的,在设计 Web 应用的时候,我们会自然而然地将系统设计与数据库存储机制联系在一起。如果使用的是 MySQL,我们就会把数据结构设计成表,如果使用的是 MongoDB,就会把数据结构设计成文档。这样会强制我们只关注事物的当前状态,我们会想“怎样保存这些数据,以便将来可以再把它们拿出来(或者修改它们)”?

这种方式存在三个问题。

1. 有悖于我们的思维

人类的思考和交流并不是以状态为中心。如果我们在咖啡店相遇,我会问你“最近可好”?如果你只是告诉我一堆状态却指望我从中猜出发生了什么事情,这是非常不合情理的。

“我有一幢房子、一辆汽车、一个冰箱、三个社交媒体账号、一只猫咪,我的右脚有点痛,我不擅长聊天,我还有另一只猫咪……”

看到没有?这样我会疯掉的。你应该告诉我,从上次见面之后都发生了哪些事情,这样我才能知道你现在的状况是什么样子的。简单地说,你应该告诉我一个故事,这个故事是由一连串事件组成的。

2. 单一的数据模型

在上图中,读写操作使用了相同的模型。我们从写数据的角度来设计表,然后基于这样的结构查询数据。这对于小型的应用来说是没有问题的,但用在大型的应用里就会有问题。随着系统的增长,查询会变得越来越复杂,总有一天,一个查询可能会包含 10 个连接操作,代码有 100 行那么多。系统很快就会变得脆弱无比,难以维护和变更。

3. 关键业务信息的丢失

这是一个大问题。在以表作为驱动的系统里,你只保存了系统的当前状态,你根本就无法知道系统是如何达到当前状态的。如果我问你“这个用户修改了几次邮件地址”,你有办法回答吗?或者我再问“有多少人把一件商品添加到购物车里,然后又移除掉,直到一个月之后才买了那件商品”,你就更没法回答了。你存储数据的方式丢掉了很多有用的业务信息!

事件溯源

事件溯源与上述的情况恰好相反,它并不关心当前状态,而是关注持续不断的变化事件。

举个例子,假设我们有一个“购物车”,我们可以创建购物车,往里面添加商品或移除商品,然后结账。

购物车的生命周期可以包含如下一系列事件:

  1. 创建购物车
  2. 往购物车里添加商品
  3. 再次往购物车里添加商品
  4. 从购物车里移除商品
  5. 结账

这些就是一个购物车的生命周期,包含了一系列事件。这就是事件溯源,非常简单吧?

几乎所有的流程都可以被看成一系列事件。在与领域专家交谈时,他们不会提及“表”和“连接”,他们会将流程描述成一系列事件以及可以应用在这些事件上的规则。

如何实施业务规则?

大部分的业务操作都有硬性约束。对于购物车来说,它的约束就是“一件商品必须先被放进购物车后才能被移除”。如果一件商品没有被添加到购物车里,又怎么能够移除它?这种事件顺序是不可能发生的。在没有状态的情况下,你怎样才能知道“购物车里是否有这件商品”?

很简单,你只要检查之前是否发生过“商品被添加到购物车里”这个事件,这样你就可以知道购物车里是否存在这件商品,然后移除它。

这样不会浪费时间吗?

一点也不。一般来说,要执行约束,只需要获得事件的一个很小子集。通过简单的数据库查询就可以获得有用的历史事件,在加载完这些事件后重放它们,把它们“投射”出来,以此构建你的数据集。这样的操作其实是很快的,因为你使用的是本地的处理器,而不是执行一系列 SQL 查询(跨域网络的调用要比本地操作慢得多,至少会相差两个数量等级)。

如何展示数据?

如果说每一个状态都是通过重放事件来获得的,那么该如何抓取数据并把它们展示给用户看?每次都需要抓取所有的数据然后再构建这些数据集吗?

答案是你没必要这样做,这样做其实是很荒唐的。

你可以在后台构建数据集,然后把中间结果保存在数据库里。这样,用户就可以在很短的时间内查询到这些数据。

有了事件溯源,你就不再局限于当前的表结构。需要做其他的查询?只要设计一个新的结构就可以了。你可以自由地实现各种读取模型,在不需要它们的时候再把它们抛弃掉。

事件溯源的好处

1. 临时的数据结构

因为所有的状态都可以通过重放事件获得,所以就没有必要把当前“状态”与应用程序绑在一起。如果需要以新的方式查看数据,直接创建新的数据视图即可。不再需要繁杂的数据迁移脚本,要做的只是创建新视图,抛弃旧视图。我现在几乎离不开事件溯源了。

2. 与领域专家的沟通变得更简单

正如之前所述,领域专家通常将业务流程描述成一系列事件,而不是状态。基于事件溯源的系统与领域专家的描述不谋而合,所以就没有必要把他们的描述转换成技术概念,这样也避免了信息丢失。与领域专家的沟通因此变得更加顺畅,因为我们正在使用他们能够理解的语言与他们沟通,这也让软件开发变得很不一样。

3. 极具表现力的模型

在事件溯源模型里,事件是一等对象,事件模型更加接近于实际的业务流程。这让很多东西都变得清晰明了,你就不会陷入存储技术的泥潭。

4. 生成报告更轻松

在事件溯源系统里,生成复杂的报告是一件轻而易举的事情。你拥有完整的历史事件,它们按照时间排序,你可以尽情地使用这些历史数据。

以之前的例子为例,假设你想知道有多少个用户从他们的购物车里移除了商品,却在一周后购买了这些商品。按照一般的开发方式,通常需要几周的时间才能开发出这个功能,而在发布之后,需要等上一段时间,等计算完所有数据之后才能生成报告。而在事件溯源系统里,你可以马上得到报告。你还可以得到之前任何一个时间点的报告,仿佛拥有了一台时光机。

5. 服务集成唾手可得

在标准的 Web 开发流程里,集成两个系统通常会导致他们之间的耦合,而事件溯源系统通过事件来解耦被集成的系统。当一个系统发生某系事件需要触发另一个系统的某个流程时,只需要写一个事件监听器即可。这种机制可以让你在不修改已有领域代码的情况下增加新的集成逻辑或特性。

例如,你想要在用户注册的时候发送一封欢迎邮件给他们,你只需要创建一个事件监听器,监听“用户注册”事件,而不需要去修改注册逻辑代码。

6. 在一般的数据库上也能健步如飞

你不需要使用多么奇特的数据库来存储事件,一般的 MySQL 数据库就足以。数据库都针对追加操作进行过优化,所以存储数据的速度是很快的。这也就是为什么事件溯源在当前的技术条件下能够良好运作,保存事件都是追加操作。

7. 可以随意更换数据库

基于事件溯源的数据结构都是临时性的,所以你可以使用你喜欢的数据库来存储状态,也就是说你完全可以选择最好的工具来完成你的工作。如果你发现了更好的工具,可以在任何时候把旧工具替换掉。我们目前正在从 MySQL 迁移到 OrientDB,可以说是轻而易举。

事件溯源的不足

天下没有完美的东西,事件溯源给我们带来了诸多好处,但也存在一些不足。

1. 最终一致性

事件溯源只能保证最终一致性。也就是说,在一个事件发生了之后,其他系统不会立即感知到它,在它们收到事件之前会有一定的延迟(比如 100 毫秒),所以你所投射的数据可能不是最新的。这看起来似乎是一个大问题,但其实不是的。例如,基于 ReactJS 构建的 Web 应用会基于用户的操作事件构建状态,查询端出现几毫秒的延迟并不会有什么问题。

老实讲,这可以说是塞翁失马,焉知非福。最终一致性的系统具有容错能力,可以解决服务中断问题。如果使用微服务架构或无服务器架构来构建分布式系统,就需要通过最终一致性来保证稳定性。

2. 事件结构发生变化

事件结构会发生变化,如果事先没有考虑到这个问题,后续处理起来会有些麻烦。如果事件结构发生变化,需要写一个更新器来转换新旧事件。转换过程可以在将数据从数据存储中读取出来之后进行。这个没有它看起来那么难,只需要准备好应对策略就可以了。

3. 开发人员需要改变思维

目前的 Web 开发主要还是以状态作为驱动,所以开发者习惯了从表的角度看待问题,而不是事件。我发现要让开发者改变思维需要一些时间,因为他们需要时间来改变习惯。最好的解决办法是让有经验的事件溯源开发者与传统的开发者结对。

总结

我很喜欢事件溯源,在构建大规模分布式系统时,它帮助我们解决了很多问题。我们可以使用领域专家能够了解的语言与他们进行沟通,我们可以自由地改变和适配系统。尽管事件溯源有一定的学习曲线,但一旦你进入到这个领域,就不会想要回头。

查看英文原文: Event Sourcing: What it is and why it’s awesome


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017 年 9 月 07 日 19:00 3412
用户头像

发布了 321 篇内容, 共 106.7 次阅读, 收获喜欢 96 次。

关注

评论

发布
暂无评论
  • CQRS 的益处

    Gabriel Schenker表示,由于没有使用命令与查询职责分离(CQRS)架构,因此当今的应用程序普遍地体现出不必要的复杂性或低效性,他相信,在基于复杂的业务线应用程序的上下文中,CQRS将成为最具实用性的架构模式之一。

    架构
  • Vaughn Vernon 谈论响应式领域驱动设计

    《Implementing Domain-Driven Design》一书的作者Vaughn Vernon最近举办了一个关于使用Scala与Akka(Actor模型的一种实现)结合DDD进行开发的讲座,这种设计方式能够避免在传统的事件驱动或Hexagonal架构中所经常面对的一些架构上的负担。

  • 给开发者的箴言:我们该如何面对和使用机器学习技术?

    本文是Guy Royse通过自己的亲身经历写给开发者们的关于机器学习的感悟。本文以第一人称翻译,讲述了对于开发者而言应当如何面对和使用机器学习技术。

  • 事件架构和事件流

    将一个单体系统迁移到分布式系统或微服务系统,通常也是从源于同一数据库的单一数据源转变为源自多数据库的多个数据源。如果使用事件架构(Event Architecture)并将所有事件持久化为数据流,那么就我们可以转回到单一数据源上。这是Ben Stopford在他撰写的博客文章中提出的,此篇博客是他关于如何在Kafka中使用事件的系列博文之一。

  • 实施领域驱动设计团队的文档指南

    对于做一个新软件项目的团队来说,应该做的第一件事就是绘制情境图,帮助他们理解情境、核心领域以及他们可能需要与之交互的其他情境,从而让所有涉及的人员都对领域有一致的理解,Paul Rayner分享经验的时候说明了实施领域驱动设计的团队应该创建什么类型的文档。

    架构
  • React TypeScript 项目基本构建

    React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据改变时 React 能有效地更新并正确地渲染组件。以声明式编写 UI,可以让你的代码更加可靠,且方便调试。

    2020 年 8 月 13 日

  • DDD 和微服务之间是什么关系?

    什么是DDD呢?DDD与微服务之间到底有着什么样的联系?DDD 资深专家、Event Storming 之父 Alberto Brandolini给出了自己的答案。

  • 领域建模:如何用事件风暴构建领域模型?

    事件风暴是DDD战略设计中经常使用的一种方法,它可以快速分析和分解复杂的业务领域,完成领域建模。

    2019 年 11 月 11 日

  • 答疑:有关 3 个典型问题的讲解

    这一讲针对3个比较典型的问题,做一个讲解,希望你也能同步思考,调动自己已学过的内容。

    2019 年 11 月 6 日

  • MediatR:让领域事件处理更加优雅

    2020 年 2 月 27 日

  • 利用事件简化系统架构

    在小型业务组件之间使用事件进行交互可以简化系统架构,上周Russ Miles在探讨“通过事件简化架构(Architectural Simplicity through Events)”时谈到了这一点。

  • 如何设计一款大型 Laravel 应用程序的架构(下)?

    本文是系列文章第二篇,主要讨论在一个传统的Laravel app中加入事件溯源。

  • Jonas Boner 谈 Events 将如何重塑现代系统

    Lightbend的创始人兼首席技术官Jonas Boner在最近的Reactive Summit 2017 会议上做了主题发言,谈到了事件驱动型服务(event driven services),以及事件驱动架构(EDA)和事件流处理(ESP)技术将会给基于分布式系统的现代应用程序的设计带来哪些帮助。

  • 第 182 讲 | 谢文杰:区块链的下一个十年

    可能这个世界还缺一次生产力的突破,突破到现有的交易网络不能承载,突破到我们需要一个更高效的交易网络。

    2019 年 3 月 6 日

  • 不可靠世界的事件溯源

    Lorenzo Nicora在最近的µCon London2017微服务讨论会上说明道:事件溯源系统的例子通常来自像电子商务这样的领域,这些领域生成事件的指令输入是面向过程的,同时这样的例子也常见于我们能够控制过程的地方。但有些领域是没有过程的,属于我们搜集外部事件的领域。这些领域的事件源本身就不可靠,传输也不可靠。

  • 领域事件:解耦微服务的关键

    这种事件发生后通常会导致进一步的业务操作,在DDD中这种事件被称为领域事件。

    2019 年 10 月 25 日

  • Dino Esposito 对于 CQRS、消息以及事件的看法

    Dino Esposito在MSDN杂志上所发表的三篇系列文章中表示:命令查询职责分离(CQRS)架构是系统架构变革的一个起点,它将对整个架构带来深远的影响。CQRS是软件架构师的设计理念从“需要持久化的模型”逐渐演变为“需要进行日志记录的事件”的第一步。

  • Greg Young:复杂事件处理的应用

    在一场报告中,Greg Young指出,复杂事件处理(Complex Event Processing, CEP)非常善于解决与时间相关的问题,比如,用户可能会通过查询历史数据来找出发生在不同时间点上却又互相关联的信息。

  • 风险管理:不能盲目乐观,凡事都应该有 B 计划

    项目中的任务,不能盲目乐观,都思考一下它最坏的结果是什么,如果最坏的结果不能接受,就说明要有个B计划,考虑风险管理了。

    2019 年 3 月 30 日

  • 分布式系统实战经验

    Camille Fournier在接受Stefan Tilkov采访时指出,我们生活在一个分布式的世界中,但那不是说我们必须处理随之而来的每一个问题。我们只需要处理那些真正需要解决的问题;要形成一种意识,企业的价值在哪里,哪里可以合理地去冒险。这会让分布式系统的构建更容易。

发现更多内容

第二周总结

qqq

极客大学架构师训练营

编程这件事

dapaul

架构师训练营第二章 总结

尔东雨田

依赖倒置架构

AIK

架构师训练营 - Task Week 2

brave heart

极客大学架构师训练营

一笔钱买两次东西?——双花安全问题分析

石君

数字货币 双花攻击 数字货币安全

依赖倒置及 Cache 重构设计

秤须苑

极客大学架构师训练营

Mybatis-plus 之 DIP

无心水

极客大学架构师训练营

极客大学架构师训练营 框架设计、设计原则、设计模式 第四课 听课总结

John(易筋)

极客时间 极客大学 极客大学架构师训练营 设计原则 框架设计

【Week02】框架设计

Aldaron

第二周作业

Aldaron

架构师训练营学习总结——面向对象的设计模式【第三周】

王海

极客大学架构师训练营

架构师 0 期第二周作业(心得体会)

何伟敏

【第二周学习总结】

黑莓

第二周 作业

尔东雨田

20年行业变革与技术演进,当下CDN如何为政企数字化转型加速?

巨侠说

CDN 边缘计算 移动视频

架构师训练营-总结2

进击的炮灰

【架构】—回归本质(面向对象)

不二架构

面向对象 架构师 极客大学架构师训练营

设计模式的主要原则

【第二周作业】

黑莓

看清远处模糊的事,不如做好身边清楚的事

Neco.W

创业心态 未知

架构师训练营 0 期第二周

Blink

小师妹学JVM之:JDK14中JVM的性能优化

程序那些事

JVM 「Java 25周年」 小师妹 JIT JDK14

课程总结

AIK

架构 0 期 -week2- 命题作业

陈俊

极客大学架构师训练营

SOLID设计原则(第二周+作业)

林毋梦

极客大学架构师训练营

架构训练营第二章作业

mh

红警1游戏开源,代码非常规范。网友:秀色可餐

程序员生活志

游戏开源 红警1

week02-总结

seki

软件架构的实现设计总结

mh

设计原则之依赖倒置和接口隔离

dapaul

2020中国技术力量年度榜单盛典

2020中国技术力量年度榜单盛典

如何理解事件溯源-InfoQ