BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

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

| 作者 Richard Seroter 关注 5 他的粉丝 ,译者 姚佳灵 关注 0 他的粉丝 发布于 2018年3月15日. 估计阅读时间: 22 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

关键点

  • 事件溯源(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

感谢冬雨对本文的审校。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT