AIGC在金融场景是如何落地的? 了解详情
写点什么

虚拟小组:利用事件溯源获得成功

  • 2018-03-14
  • 本文字数:7096 字

    阅读完需:约 23 分钟

关键点

  • 事件溯源(Event Sourcing)的做法有点像分类账。每个跟一样“事物”(又名“聚集”)有关的事件被记录在一个持久化的日志中(又称事件存储)。无论何时需要知道某个事物目前的状态,都可以通过“重放”所有的事件到事物重新创建出来。
  • 要重视我们有界上下文的边界和识别,并且我们在代码中非常仔细地管理域之间的边界也很重要。这意味着在特定的事件上要避免跨域的依赖关系。
  • 开发人员是否应该定制 CQRS 和事件溯源框架的核心部分?不应该。
  • 各团队必须做出两个互斥的选择:是否选择事件驱动架构?要不要应用事件溯源?

事件溯源的想法早就有了。但是最近,我们看到了这个数据存储和检索模式的更多。什么时候该用这种方法?架构的影响有哪些?哪些地方依赖平台还是应用框架?为了回答这些问题,InfoQ 请了两位专家给予帮助。其中Ben Wilcock 是 Pivotal Labs 的高级方案架构师,Allard Buijze 是 AxonIQ 的创始人兼 CTO,AxonIQ 就是广受欢迎的 Axon 框架的幕后公司。

InfoQ:请问什么是事件溯源?能否给我们举个实例,说明这个模式是对更为传统架构的改进?

Buijze:事件溯源是一种存储实体的风格,不是直接存储状态,而是一系列事件(比如,事件流),描述了过去在实体上发生的所有变化。实体当前的状态是通过在一个“空”实例上重放所有发生过的事件重新计算后得出。一般来说,事件流描述了在一个聚集上发生过的变化:一组实体被看做是状态变化的单位。例如,不再存储订单的状态,而能够查看特定物品订购和取消订购的情况,有了事件溯源,可以存储订单创建、加入了一些物品、移除了一些物品、确认订单、然后发货,然后取消的事实。事件溯源包含了很多可能有价值的信息,但是如果只存储状态,这些信息就会丢失。

事件溯源有很多优点(当然也有缺点!)。例如,它提供了一个可靠的审计追踪,可以查看某个组件过去发生过什么事。那是因为它不会把这个“追踪”作为副产品来存储,而是把它作为当前状态的唯一依赖。所做出的任何决策能用这些过去的事件来解释。

在我参与的一个项目中,我们使用事件溯源使审计就像应用的“原生”功能一样。我们知道这些信息对分析来说也会有用,但是还根本不知道如何使用。该应用程序是一个在线纸牌游戏(叫做桥牌),能让用户打锦标赛并赢取真正的现金奖金。和扑克锦标赛有点类似。因为涉及大笔现金奖励,审计是非常重要的。

我们还没对项目深入研究,就发现我们不喜欢在“游戏引擎”中使用的领域模型,即实现游戏规则的组件。因为我们使用了事件溯源,我们所有的测试都是“给出过去的事件,当执行这个命令时,期望发布这些新事件”这样的风格。无论是命令还是事件,都是通过功能需求驱动的,而不是由技术驱动。这意味着我们能让这些测试保持不变,可任意重新实现“游戏引擎”。

一段时间之后,随着系统的积极使用,审计追踪已经证明它是有效的:产生了很多用户有“可疑举动”的投诉。首席开发员决定创建一些分析工具来调查一下。通过重放过去的事件,在此基础上创建不同的视图模型,他设法揭开了共谋玩家的网络,这个网络增加了他们获胜的机会。他们设法在支付之前,从其账户中获取大笔金钱。

如果只存储当前状态,揭开这个欺诈所需的信息有很大可能已经丢失了。

Wilcock:当我和客户在一起时,我尝试用他们能产生共鸣的方式来解释事件溯源,并映射到他们所认知的领域。所以如果我和零售商在一起工作,我也许用“产品目录管理”做例子。传统上,如果开发人员被要求为这样的领域开发一个解决方案,他们或许首先会从基于持久层的 CRUD 的设计而不是事件溯源开始着手。然而,很快,因为领域真实属性被发现,人们很快就会明白,给每个产品限定为一行记录是不够的。事实上,产品加入目录是通过一个复杂的生命周期完成的,其中充满了关键决策点(事件),出于各种原因,这些点需要进行认真地追踪,这些原因包括:监管及竞争合规性、安全因素、盈利因素、供应链完整性因素、运营因素和许多其他影响产品进入目录的过程,还有产品在目录中时的产品生命周期。

当我们尝试利用纯粹的基于 CRUD 的模式来处理复杂的需求时,很快就需要开始增加“审计表”、“提示信息”、“关系”、“回滚”和“报告”,并且所有这些大大增加了我们 CRUD 模型的复杂性。一开始看起来是“简单可行的事”,很快变得非常复杂,而不是一个特别整洁或优雅的解决方案。以 CRUD 开始着手,我们也许很快会陷入困境。

出于这个原因,我尝试在设计过程中尽早给客户介绍事件溯源。事件溯源有着根本性的不同。它是像我刚才描述的一个复杂领域的真实属性驱动和塑造的。事件溯源对待领域事件像是方案设计的一等公民(而不是像用 CRUD 那样经常是事后考虑因素)。对于那些刚接触这个概念的人来说,事件溯源的运作有点像分类账(是我们所知的最古老和最成功的记录保存系统)。每个事件和一个“事物”相关(又名“聚集”)被记录在一个持久的日志中(又名事件存储)。无论何时需要了解某个事物的当前状态,可以通过重放发生在该事物上的所有事件来重建。因此,例如,如果我们在追踪一个产品的 RRP,我们用 PriceChanged 来做,那些事件也许会显示最初是 Bob 设置价格为 $199,然后 Jill 把价格降到 $149,现在的价格 $109 是 Jane 改的。“事件存储”会为该产品保存所有这些事件,同时可以通过“重放”PriceChanged 随时获得该产品的当前价格。

这个方法的美妙之处在于无需任何单独的审计或价格历史表。我们知道 Bob、Jill 和 Jane 在过去的某个时间点改动过产品价格,因为我们保存了所有这些事件并通过它们来“溯源“当前的产品。我们还可以很容易回到过去显示历史价格。最后,我们能回顾性地添加有意义的完整历史报告,比如“谁是最早的价格变动者”、“哪个季度的价格波动最大”以及其他业务洞察。因为我们有所有事件的记录,可以做很多非常齐整的事情,能建立一个系统的新版本,查看在投入生产前用真实事件的记录会有怎样的表现!

当然,事件溯源最初会有一条相应的学习曲线,但是一旦考虑到企业规模领域的真正复杂性,通常它是更优雅的解决方案架构。

InfoQ:在一个事件溯源的世界里,我们该怎样思考跨领域交互?在 Ben 举的例子中,我们是否需要维持 PriceEvents 和另一个“事物”跨聚集边界的一致性期望?在长期运行的 Sagas 中,这个如何做出来?

Wilcock:我认为我会把它作为更普遍的领域驱动设计(Domain Driven Design)问题来解释,而不是一个纯粹的事件溯源问题。因此,我建议读者找找 Vaughn Vernon( @VaughnVernon )的《DDD Distilled》和《 Implementing DDD》来看看,以获取一些该主题相关的伟大见解。

关于如何思考跨域交互的具体主题,我认为重要的是尊重我们有界上下文的边界,认识到我们在代码中非常小心地管理领域之间的边界的重要性。我们也许希望避免的是引入在特定事件上跨领域的依赖关系(像 PriceChanged 事件)。如果我们这么做(比如,在跨有界上下文中共享事件的类定义或格式),就会减弱我们独立编写代码的能力,并在无意识的代码中引入物理绑定。

当然,这会影响我们对一致性的看法,但是我认为每个相信 DDD 和微服务想法的人也许已经接受这么一个事实:要保持跨领域的一致性是困难的,从而可能接受最终一致性和松耦合。正是出于这个原因,像防腐层(Anti Corruption Layer)之类的模式是 DDD 架构的一个共同特征。

Buijze:从本质上说,事件回溯的概念本身从没跨界。尽管确切的定义不同,我认为事件溯源是将对象的状态作为一系列事件(而不是状态)持久化的选择。一个对象是如何被持久化的,不应该泄漏到组件之外,更不用说上下文了。

然而,在实践中,描述对象更改的事件通常对其他组件非常有用,它们常被用于同步不同的模型。例如,应用 CQRS 时在同一上下文或不同上下文中更新视图模型。

正如 Ben 所指出的,在后一种情况下,需要额外的预防措施以确保在上下文之间不产生不想要的耦合。要避免的一件事是简单地发布所有事件给任何组件使用。举个例子:一个 Shipping 模块想知道下订单的时间。但是,Order 模块不会精确地发出该事件(Order 比这个更棘手)。相反,它发出 OrderCreated、ItemAdded、ItemRemoved、PaymentInformationRegistered 和 OrderConfirmed。Shipping 模块需要监听所有这些事件才能获得其工作所需的信息。比监听所有事件更糟的是,它也重复了很多与处理这些事件相关的“逻辑”,比如如何匹配 ItemRemoved 和 ItemAdded。

用到跨上下文时,这就是应该仔细处理事件的地方。通常,我们建议客户只在处于同一上下文的组件之间共享事件。为了在上下文之间同步组件,需要以更粗粒度的事件来描述发生的事。一个解决方案应该发出一个不同的事件,比如确认订单时,OrderPlaced 包含相关的订单详情。另一种方案是把 OrderConfirmed 事件看作所谓的“里程碑”事件,让它包含更多相关信息,这样它在 Order 上下文之外也有价值。

两种方案各有优缺点,在 DDD 中被看作是策略设计(Strategic Design)的一部分。

在这个意义上,Sagas“只是一个部件”。比如,它们处于同一上下文中,处理低层次的跟订单相关的细节,或者处于不同的上下文中,协调一些更高层次的流程,例如确保在下了订单后生成发票并计划发货。它们有助于减少上下文的直接耦合,因此发货模块不需要确切知道“何时”创建发货。Saga 可以协调。

InfoQ:在事件溯源中,应用程序框架与平台组件的作用是什么?也就是说,在一个 ES/CQRS 体系结构中,我们应该期望从事件流处理器或数据库,还有像 Axon 之类的 Java 框架中得到什么?此外,开发人员是否应该定制构建这个架构的核心部分?

Wilcock:我会请 Allard 来比较一下框架、流处理和事件溯源。但是对于这个问题,开发人员是否应该定制构建这个架构的核心部分,我的回答是很坚决的,不应该,就算他们有这个选择也不行。

最近,这个同样的问题出现了几次,我的回答一如既往,不要做。设计、测试和交付一个安全的生产级 CQRS 和事件溯源架构是相当困难的(我确信 Allard 会在这个问题上支持我),而且也容易出错。那不是说做不到,可以做的,但是改变不了“非竞争要素带来的开销”这个事实。

在 2018 年建立自有的 CQRS/ES 架构肯定是没有价值的,它不会给常规的银行、零售商、生产商、服务供应商等等增加任何价值。要开发人员在一个 DIY 方案上工作也许在技术上有点吸引力,但是这是一项复杂的工作,需要时间、智力和金钱,而那些真正重要的是吸引新客户或者增加利润。

对我来说,更明智的做法是去找有声誉的开源替代品,从它开始入手。有很多可靠的选择:对于 Java 开发人员来说, Axon 框架是个非常好的选择。它成熟、稳定、可扩展、和 Spring Boot 配合得很好,在各行业中有一些很好的参考用户。.NET 上的开发人员和用 Node( Wolkenkit )及一些其他编程语言的使用者一样有很多不同的选择(比如 Brighter )。我会先调查这些替代品,然后再做。因为它是开源的,所以如果真有必要,稍后你总是可以复制出自己的分支的。

Buijze:显然,作为 Axon 框架的创作者,我是完全站在它这边的,但是我也完全赞同 Ben 所说的。我想补充一点 Axon 框架存在的理由,我在开始用 CQRS 和事实溯源实现一个应用程序的时候,发现需要大量的“管道”。我就开始分享代码中的通用部分,大家去复用它们。那是 2010 年年初。因此,你再自己做一套就意味着你忽视了我这 8 年的经验。

如今,我把 Axon 框架定位于一个帮助分离业务逻辑和基础设施逻辑的框架,允许开发人员用 DDD 原则实现业务逻辑,提供开箱可用的(通用)基础架构元素。显然,组件需要交互。我们已经确定了这种交互的 3 个原因,每个有自己的消息类型:命令——系统必须执行某些操作(比如,更改状态),事件——通知发生了什么事,以及查询——请求信息。每个消息有非常不同的路由模式。

使用适当的抽象时,组件不需要知道确切的用于传送消息的基础架构组件。甚至组件是否作为一个独立部署单元的一部分,还是在不同的单元中部署,这些都没关系。必须选择 / 设置基础架构元素以匹配选定的部署风格和非功能性的需求。这正是选择事件流处理器的地方。这是一个应用程序框架的角色,以确保能用适当的抽象访问这些处理器,因此,业务逻辑就不会受到实施特定选择的约束。

在 AxonIQ,我们已经注意到大多数数据库(特别是关系型的)适合作为事件存储提供服务。但是,当在数据库的数据量增加时,性能会受到影响。尽管我们有种自然的倾向,认为 NoSQL 是解决方案,事实上它们比起好的古老的 DBMS 也没有多少优化。重要的是明白数据库(特别是 NoSQL 实现的)做了什么选择。这些选择通常和事件存储实现的期望有冲突。这就是我们为什么在最近发布 AxonDB(一个为事件溯源而存储大量事件的优化数据库)的原因。

关于开发人员是否应该创建自己的核心部分的问题,我的回答是否定的。可惜,我们有时不得不下结论说,还没有什么太合适的选择可以匹配我们的非功能部分。

InfoQ:对那些有很多团队的大型组织中,尝试事件溯源的团队,您有什么建议?陷阱在哪里?什么应该分享,什么不应该分享?

Buijze:我的建议会是两个明显不同的选择。一个是是否选择事件驱动架构,另一个是是否应用事件溯源。虽然这两种技术能相互促进,但是两者之间没有严格的依赖关系。

在组件之间的通信中是否把事件作为主要元素是一个架构决策。所有的(至少是大多数)组件都应该支持它,以保持真正的价值。我相信之前的问题的答案已经表明,在建立复杂系统时,使用事件是非常强大的方法。

是否使用事件溯源是每个组件要做出的决定。这可以是架构层面的指示或指导,跟何时做或不做有关,但是个本地决策。就算不是在内部用那些事件作为唯一的状态来源,也仍然可以发布事件。你仍然可以查看发生了什么(通过存储发布的事件)和异步通知其他组件,通过这些能力来获益。它作为审计线索的可靠性不高,因为不能保证状态和历史的匹配。这就好像把一个小分类账放在 Kenny 的涂鸦墙边上,所有覆盖之前作品的艺术家都必须签字。这个主意不错,但是有时有很大的可能会出现条目丢失的情况。没有审计追踪也许比有个证明你做错了的东西更好。

我也敦促那些在大型(分布式)系统上工作的人,特别在用事件溯源架构时,要读读域驱动设计(Domain Driven Design)。特别在关于有界下上文部分有一些很好的指导方针,用于选择在哪里放置某些逻辑和如何在组件间设计交互。仔细选择依赖关系的方向。事件是反转依赖关系的好方法,但是我们不要忘了还有命令(Commands)和查询(Queries)。

最后,我很高兴看到在现代架构中,事件占据了更主导的地位。我们必须要小心谨慎,不要过度反应,觉得处处有事件(小心不要因为手里拿了锤子,就到处是钉子)。我们也不要忘了命令和查询,要给予它们同样的关注。

Wilcock:我可能会先说孤立的事件溯源绝对是有用的,但是用于补充 CQRS 架构和域驱动设计时,它的功能和潜力被放大了。使用孤立的事件溯源的一个缺陷是,它会被简单地看作是一个持久性机制的替代品,但是这破坏了其更广泛的潜力,它有能力(连同 CQRS 和 DDD)把分布式事件驱动架构放到一个优雅和可扩展方法架构的中心。类似的,如果事件只是简单地“出现”,不带有追溯性或者“因果关系”,无法回溯出是哪些命令产生了它们,那么似乎也错失了让其充分发挥的良机。命令产生事件,这对很多系统问题来说,的确是个优雅的解决方案(在我的书里,远比传统的 CRUD 方法优雅)。

在大型组织中,另一个潜在的缺陷是团队之间的共性或标准化。如果有很多团队,各自都有实现事件溯源的方案,会导致时间、金钱和智力的极大浪费,未来几年将出现显著的维护难题。在我看来,更好的方法是从以一个共同的框架作为基准开始,并且只有在充分理由下才能偏离此框架。CQRS 和事件溯源是一个已被解决的问题。不要重新造轮子,更不要同时有 10 个团队造轮子。

最后,对在团队间分享什么事件数据要万分小心。有些事件可以共享,但很多是不能分享的,不要引入团队间的依存关系和耦合,在未来会很难解耦。实践证明,像“防腐层(anti-corruption layer)”之类的模式可以有效防止这些无意识耦合,但是也会给你的工作增加一些复杂性和开销。无论你做了什么决定,小心设计事件、思考在事件中应该有什么数据、什么可以不用管、考虑事件分类以用于识别和区分“内部”和“外部”事件时是有帮助的。设计正确的事件驱动解决方案会是个挑战,但是回报是巨大的!

小组成员简介

Ben Wilcock 是 Pivotal Labs 的高级解决方案架构师。他帮助 Pivotal 的财富 500 强客户利用 Pivotal Application Service(PAS)和 Pivotal Container Service(PKS)让云本地化。Ben 对 CQRS、事件溯源、微服务、云和移动应用充满热情。他也是一位成熟的技术博客作者,他的文章在 DZone、Java Code Geeks、InfoQ、The Spring Blog 等等上面都有介绍。你可以关注他的推特账号 @benbravo73 ,阅读他的博文

Allard Buijze 是 AxonIQ 的创始人和 CTO。从 6 岁开始,他就对编程产生了极大的热情,已经指导了大大小小的组织构建高性能和可扩展的应用程序。现在,他正在致力于利用域驱动设计、命令 - 查询责任隔离和事件驱动架构的概念让大型系统的实现更容易。最初,作为一个实验,他创造了 Axon 框架,但是当大型机构和组织都开始使用 Axon 作为他们复杂问题的解决方案时,AxonIQ 就诞生了。他的信念是只有通过和他人不断及密切的交流才能得到好产品,藉由此理念,Allard 现在成了经常在会议和聚会上发表演讲的人,他乐于为开发人员和架构师们提供培训。Allard 也经常出现在董事会上,为高级管理人员解释 DDD、CQRS 和 EDA 的概念和价值。

阅读英文原文: https://www.infoq.com/articles/panel-event-sourcing

感谢冬雨对本文的审校。

2018-03-14 18:201556
用户头像

发布了 199 篇内容, 共 79.2 次阅读, 收获喜欢 292 次。

关注

评论

发布
暂无评论
发现更多内容

全栈混合云综合架构方案研究和落地

天翼云开发者社区

详解GaussDB(DWS)的query_band负载识别与应用

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

字节跳动的开源历程与价值思考

字节跳动开源

开源 云原生 演讲 字节

亚信科技AntDB数据库荣膺第十二届数据技术嘉年华(DTC 2023)“最具潜力数据库”大奖

亚信AntDB数据库

AntDB AntDB数据库 企业号 4 月 PK 榜

数字先锋| “智慧旅游”新模式,天翼云助力张家界旅游产业创新发展!

天翼云开发者社区

【转载】“一中心四辅助” | 亚信安慧产品与解决方案全栈图谱发布

亚信AntDB数据库

AntDB AntDB数据库 企业号 4 月 PK 榜

AI+算力,赋予天翼云数字人“最强大脑”!

天翼云开发者社区

高性能网络SIG月度动态:virtio-net 支持动态中断调节,SMC v2 协议增加新扩展

OpenAnolis小助手

ebpf 高性能网络 龙蜥社区 sig 月度动态

Android技术分享 | 一行代码实现屏幕、声音采集

anyRTC开发者

音视频 移动开发 Andriod 屏幕采集 声音采集

技术文章的写作思维

老张

技术干货 技术文章

如何在 Postman 中进行 HTTPS 请求

Liam

Java 程序员 后端 Postman API 调试

PaddleSeg 2.8版本正式发布啦!

飞桨PaddlePaddle

飞桨

golang-GMP模型

LED显示屏室内改为户外为何不可取?

Dylan

LED显示屏 户外LED显示屏 户内led显示屏

惠普聚焦资源再利用、森林保护和碳排放,理念贯穿电脑全生命周期

叶落便知秋

“天翼云出海友好客户启航会”圆满收官!

天翼云开发者社区

[ChatGPT 勘误] 关于 CL_WB_PGEDITOR 的用途

Jerry Wang

编程 abap Netweaver 思爱普 三周年连更

【堡垒机小知识】堡垒机有主机监控功能吗?

行云管家

网络安全 堡垒机 主机监控

原生开发能不能动态化?如何选择动态能力建设流派

没有用户名丶

三分钟完成静态网站托管

华为云开发者联盟

开发 华为云 华为云开发者联盟 企业号 4 月 PK 榜 静态网站托管

北京 Meetup 邀你来|云上 StarRocks 极速湖仓

阿里云大数据AI技术

大数据

天翼云赋能芦山县医共体建设,为群众铺就便捷顺畅就医路

天翼云开发者社区

【致地图开发者】地图开放平台假期服务公告

百度开发者中心

百度地图

DevData Talks | 微众银行有哪些研发效能实践与思考?一起来拓展认知边界!

思码逸研发效能

研发效能 金融 微众银行

Fabarta 图增强数据血缘治理解决方案

Fabarta

数据治理 图数据库 图智能 血缘治理

轻松比较文件和文件夹:Beyond Compare 4 Mac中文

真大的脸盆

Mac Mac 软件 对比工具 比较文件 对比软件

为什么众多大型国企都在升级企业数智化底座?

用友BIP

技术大会 用友iuap 用友技术大会 升级企业数智化底座 央国企数智化转型

服务器通用背板管理(UBM)实现

天翼云开发者社区

【等保小知识】等保一级需要备案吗?

行云管家

等级保护 等保备案 等保一级 一级等保

电商流量分析怎么做?试试这款数据工具DataLeap!

字节跳动数据平台

大数据 用户增长 数据产品 电商 企业号 4 月 PK 榜

【Linux】之【内存】相关的命令&&解析以及内存相关的问题[free、meminfo、内存泄漏、内存溢出、Overcommit]

A-刘晨阳

Linux cpu 三周年连更

  • 扫码添加小助手
    领取最新资料包
虚拟小组:利用事件溯源获得成功_语言 & 开发_Richard Seroter_InfoQ精选文章