复杂软件设计之道:领域驱动设计全面解析与实战
上QQ阅读APP看书,第一时间看更新

2.4 通过事件风暴会议发现有界上下文

Google产品技术经理Steven A. Lowe认为:事件风暴是一种快速、轻量级且未得到充分认可的群体建模技术,它对于催化并加速小组的学习是非常强大、有趣且有用的。作为Alberto Brandolini的心血结晶,它是Gamestorming和DDD原则的综合实践。

事件风暴是软件系统的快速设计技术,涉及技术人员和领域专家/业务分析师。该技术不限于软件开发。您可以将其应用于几乎任何技术或业务领域,尤其是那些大型、复杂或两者兼有的领域。

事件风暴是围绕领域事件展开的头脑风暴会议,领域事件是什么,这是领域专家对其业务领域感兴趣的、发生的事情与情况。领域专家对数据库、网络接口或设计模式并不感兴趣,但关注业务领域中会发生的事实(动词),而领域事件就是捕获这些事实。

20世纪颇具影响力的哲学家维根斯坦说:世界是事实的总和,而非事物的总和。而传统哲学的观点是:世界是由存在着的万物组成的,他认为是世界是由发生着的事实组成的,这是他的《逻辑哲学论》中的基本出发点。“存在着的万物”和“发生着的事实”两者的区别其实是主语和谓语动词的区别,“万物”通常是主语名词,而“事实”是一种动态的过程活动,涉及谓语动词。从谓语动词中才能抽取出“万物对象”等名词的概念,他的逻辑分析观点认为:重要的不是对象,而是事实中对象之间的关系,对象所处的情势(事情的现实状况与发展趋势),对象只能在事实(对象之间的关系)中存在,离开事实的对象就毫无意义了。

在Stackexchange(https://philosophy.stackexchange.com/questions/47705/what-are-the-differences-between-facts-and-things)中有一段对于维根斯坦的“事实”的解释。

事实包括所有事物的存在,以及所有事物所有可证明的属性和行为(注意“行为”二字)。

想象一下,宇宙如果只由一个茶杯和一把锤子组成。

事物(Things)是:茶杯、锤子、宇宙。

事实(Facts)是:事物的存在是事实。

● 有一个茶杯。

● 有一把锤子。

● 有一个宇宙。

此时两个视角是相同的,然后关于事实:

● 茶杯靠近锤子。

● 茶杯和锤子正朝着彼此移动。

● 茶杯和锤子将继续朝着彼此移动。

关于事实的事实:任何移动的东西都将继续移动,除非有什么东西阻止它。在这一点上,已经完全超越了事物并开始谈论一般事实。

维根斯坦也讨论假设事实:

● 茶杯可能比锤子大。

● 可能有两个茶杯。

在DDD建模领域,使用“领域事件”代表“事实”,表示对象之间的关系,既然领域事件表达了关系,需要涉及至少两个对象,领域事件通常也用于表达两个以上有界上下文之间的通信交流的方式。

通常情况下,人们的直觉是更关心主语,“哪些对象”发出领域事件才是人们关心的,但是维根斯坦认为应该倒过来,领域事件代表的“事实”概念重要于“对象”概念,它们的主次顺序与直觉相反。在事件风暴会议中,只有先找到发生的事实,将其标记为领域事件,才能发现这些事实涉及哪些对象,只有找到事实,才能分析出对象,对象之间的结构边界才能得到划分,而划分了边界的对象才可能是DDD中的有界上下文。

在进行ER数据模型分析时,一般是先有实体(Entity)表,然后才表达关系(Relation),维根斯坦逻辑思想的革命性在于:先有关系,才有实体,实体在关系中才有意义。DDD中的领域事件、有界上下文和聚合都是这种关系的不同表达形式,或者说,数据库的关系表往往才是关注的重点,它是业务逻辑的关键所在。

以上是事件风暴会议等方法论的哲学背景介绍,有了这些认识,才可能真正认可基于领域事件的事件风暴会议的价值所在。

事件风暴不同于其他建模。

1)如果首先从数据建模开始,那么人们的思考和对话将很快转移到模式、事务和其他与业务领域无关的事情。

2)如果首先从行为建模开始,当人们将行为分解为任务并将其链接到流程时,会分心或不知如何下手。

虽然有很多方式来表达数据和行为的结合,但领域事件可能更合适。由于领域事件表示的是领域的事实,这些事件仅在基础业务发生更改时才会发生明显变化,因此,领域事件是DDD建模中更稳定和更具弹性的脚手架。

事件和状态是一对同等词语,发生了事件其实隐含了事件的状态变化,例如某处发生爆炸事件,这是事实,爆炸后的现场状态肯定和爆炸前不同,使用数据库表记录的大部分是状态,因为只有状态才是数据,而行为不是数据。Rinat Abullin说:行为不是数据。更确切地说,数据通常是在固定情况下观察到的行为的近似值。

在领域驱动建模中,状态被看成事件的副作用。当且仅当具有影响新行为的潜力时,状态才有意义。有界上下文中的聚合(后面章节细谈)不仅仅是一些表示状态的对象的组合,聚合是对相关行为的汇聚,并仅将状态保留在与对应行为相关的位置。

2.4.1 领域事件

事件代表过去发生的事件,它既是技术架构概念,也是业务概念。以事件为驱动的编程技术模型称为事件驱动架构(EDA),这里强调的是事件的业务概念。

一个事件代表某个已经发生的事实,在计算机系统中,事件是由一个对象表达,其包含有关事件的数据,比如发生的时间、地点等。这个事件对象可以存在于一个消息或数据库记录或其他组件的形式中,这样一个对象称为“一个事件”。

事件概念在业务系统中应用,诞生了领域事件和事件溯源等DDD实现方式:通过引入事件,像引入服务概念一样,跨越业务和技术鸿沟,同时又能表达函数编程式思维。在业务上将事件和DDD结合在一起,可以形成统一语言DSL。

前面已经谈到,领域事件代表业务领域中发生的事实,通过领域事件捕捉这种事实,表达形式可以很自由,但还是有一些约束。

这里以电商领域为例,用例场景描述是:用户挑选自己喜欢的商品,将它放入购物车,当然也可以从购物车中移除,当完成购物后,用户会确认生成订单,并进行支付,而商家根据订单进行发货,用户收到货后会确认收货。

在这个领域描述中有多少发生的事实?首先从动词入手,而不是名词。名词是人们直觉感觉到的,比如“用户”、“商品”等,但是这些并不能代表发生的事实——世界不是由对象名词组成的,而是由动词事实组成的,那么“挑选”“放入”“移除”等动词会跳入眼帘,但是也不需要纯粹的动词,至少宾语名词还是需要的,因此,“挑选商品”“放入商品到购物车”“从购物车中移除商品”成为关注的目标。“挑选商品”是用户的一个主观自由行为,可以通过推荐影响其挑选结果,但是怎么挑选还是其自己决定的,这不是解决方案能够左右,排除在问题空间之外。下面继续分析“放入商品到购物车”和“从购物车中移除商品”。

首先分析第一个功能:“放入商品到购物车”,或表达为“将商品放入购物车”。这两者是一个意思,这条语句的谓语动词是“将商品被放入”,宾语是“购物车”,动词代表事件发生,事件是发生的事实,这里事实是什么?如果对名词比较敏感,那么可以从名词中寻找事实。事实代表名词对象之间的关系,“商品”与“购物车”是两个名词对象,它们之间因为“商品加入了购物车”而发生了关系,所以,事实是“商品加入了购物车”。这个事实还有另外一种表述:“购物车中加入了商品”,两者的区别是“商品”和“购物车”两个名词哪个放在首位。区分这两种表述有重要意义,首位一般有主语的意思,隐含意义是当前边界的主人,这里发生的事实是在哪个主人的边界中发生的呢?“购物车中加入了商品”暗示当前动作发生的背景是在“购物车”相关场景,而“商品加入了购物车”则暗示了是在“商品”相关场景,商品的场景是“商品管理”,这里的动作并非有关商品管理,而是商品的使用、用途,它被用在了购物这样的场景,经过如此分析,选择“购物车中加入了商品”这条语句,这句话暗示了当前场景是“购物”,该场景可以总结为有界上下文“购物”或“购物车”,前者是动词,后者是名词,最终选择取决于统一语言偏向。

当找到有界上下文以后,需要命名发生的领域事件了。这里的领域事件是“购物车加入了商品”,这代表商品与购物车发生关系,但是这个关系不是抽象永恒的关系,而是在时间和空间上非常具体的关系,如果具体化?这就必须指定发生的具体时间、具体规格(如数量、价格)等,这些都需要量化。因此,“购物车加入了多少商品”必须指定清楚。那么如何表达“多少商品”呢?“多少”的含义其实是代表“商品数量”,因此,多少商品=商品数量+商品名称。但是必须使用领域中的一个通用术语表达这个复合组成关系,那就是“条目”,英文“Item”,这样,“购物车加入了多少商品”就表达为“购物车条目加入到了购物车”。这里再次将“购物车条目”置于首位,它才是应该被主要关心的,因为现在已经进入购物车内部边界,在购物车有界上下文讨论这个事件,那么这个领域事件就表达为“ItemAdded”,名词在前面,过去式动词在后面。

领域事件的命名需要使用过去式,表示已经发生的事情,如“CustomerRelocated”表示客户已经被重新分配;“CargoShipped”表示货柜已经装运。因为领域事件表示与领域相关的事件,不能简单说员工已创建、已删除,而是已雇用、已解雇、已退出。命名需要代表深刻的业务领域含义,以下一些命名可能意味着命名者没有深入理解领域知识。

最明显的是“CRUDish”,如员工已创建、已删除,或像“SomethingChanged”“SomethingUpdated”这样的事件只是表示简单的数据改变、更新,没有使用统一语言知识的术语表达。只是简单的数据更新可能没有必须使用DDD,其实事实并非如此,这表明没有进行足够的领域探索,或者在行为领域方面的探索很差,可能缺乏对业务流程的理解。

但是,“SomethingAssigned”“SomethingUnassaigned”这样的命名可能代表流程中的分配任务等概念,但这是流程中的通用术语,不是领域中的通用术语。还有技术名词,如“SomethingRollbacked”“Canceled”“Patched”“回滚”“取消”等也都是通用的事务或交易术语,并不是领域中的通用术语。

2.4.2 命令

事件的发生是有原因的,是什么触发了事件的发生呢?这里引入“命令”这个概念。它表示事件的触发器,通过UI(界面)生成,那么这个命令就是由用户点击“确认”等按钮触发的。从UX(用户体验)设计角度看,命令体现了人的一种意图,因为有这个意图,所以才触发这个命令。命令进入领域系统后,系统是否响应执行这个命令,还是要依据领域自身的逻辑:如果逻辑允许,领域系统正确执行了这个命令,那么意味着系统内已经发生了一个事实,这个事实就是领域事件;如果违背领域逻辑,系统可以拒绝执行命令。

命令和事件的区别可以总结如下。

1)命令表达一个正在发生的动作,有待执行;命令是职责目标,是方向的确定,是主动的。

2)事件代表已经发生的某个事情,必须响应;事件是不可控的,因为已经发生,无法改变。

3)命令是请求执行,而事件是执行已完成。

命令命名和事件命名也有区别的,命令一般是动词在前,或结尾使用“ing”表示正在发生的动作,区别于事件命名中结尾使用“ed”表示过去式的动作。

命令+事件的模式符合意图、执行和结果的范式,日常生活中这种方式很多,例如上级给下级命令,下级一定要执行,并返回结果;给洗衣机发出洗衣命令,洗衣机执行,最后获得洗衣完成的结果;在浏览器中填写完表单,单击“提交”按钮,后端系统将执行,然后返回结果。

命令在领域事件之前,是领域事件发生的因,但是在一个业务流程中,上一环节的事件可能直接变成下一环节的命令,这样就组成了一条因果链,通过这样的命令+事件建模分析,可以发现流程能力环节和有界上下文,如图2-9所示。

图2-9 事件转命令

前面章节讨论了不同有界上下文之间的关系,其中有主机服务、发布订阅等形式,这两种方式结合命令/事件时有什么具体不同呢?

1)主机服务是一种API直接访问的形式,API提供者被其他有界上下文直接调用推动。在主机服务情况下,几乎不涉及命令和事件的概念。

2)发布订阅则属于一种订阅模型,事先订阅某种类型的领域事件,在运行时,当其他有界上下文发布该领域事件时,将触发订阅上下文做出相应的反应。

命令/事件只适合在发布订阅模型中,发布和订阅两个有界上下文通过领域事件联系在一起。如果不希望通过具体领域事件耦合,可以通过已发布的语言,例如工作流的BPMN规范。在BPMN中约定了标准的事件和节点类型,只要双方有界上下文遵循BPMN规范就可以实现相互通信。

发布订阅模型适合一个流程中不同有界上下文环节之间的通信,它不适合在业务和非业务之间的上下文映射。假设有一个用于发送通知的非业务有界上下文,它可以发送各种形式的通知,例如Email、短消息和微博。考虑以下两种实现方式。

1)主机服务:通知上下文提供一系列API供外界访问,其他有界上下文直接调用这些API来驱动发送各种通知。

2)发布订阅:通知上下文需要事先订阅那些需要发通知的有界上下文,等待这些有界上下文发布相应的事件以后就会发送通知。

在发布订阅模型中,通知有界上下文和其他上下文通过领域事件耦合在一起,通知上下文可能介入领域事件与自己发送的通知之间的翻译转换工作,那么就需要一个专门的有界上下文负责实现翻译,这种翻译也就是事件到命令的转换,将其他有界上下文的事件转换成自己发送通知的命令。

这样做的问题是:只要增加一个新的领域事件就要涉及翻译有界上下文的变动,翻译有界上下文变成了共享库。为了避免共享库,变通办法是规定一种已发布的语言,已发布的语言是有界上下文生成的消息格式的规范或协议。

如果使用主机服务模型,不涉及命令/事件概念,两者直接通过API耦合在一起;如果为了彻底解耦,可以引入防腐层上下文,这个防腐层实际也是一个专门的有界上下文,负责事件转换命令。

主机服务模型虽然没有命令/事件的概念,好像比较简单易行,但是如果用在两个有界上下文之间,可能会造成两个上下文之间过于耦合,界限划分不清晰,API的接口一旦变动,对方上下文变动很多,双方团队就需要在一起协商开会,效率会大大降低。因此对于通知这类通用的技术性质有界上下文,可以归类到基础平台共享库中,由专门的平台部门负责,可通过云平台方式提供。

2.4.3 事件风暴建模法

事件风暴(Event Storming)是一种用于快速探索复杂业务领域的研讨会格式,EventStorming.com的创始人Ziobrando认为它有以下特点。

1)功能强大:能够在数小时而不是数周内提出完整业务流程的综合模型。

2)很有吸引力:整个想法是将提出问题并将知道答案的人带到同一个房间,并共同建立一个模型。

3)它是有效的:得到的模型完全符合DDD实现风格,并允许快速判别状况和聚合边界。

4)容易:符号非常简单。没有复杂的UML,UML可能会导致参与者在讨论核心时由于各自理解不同而讨论中断。

5)有趣:能愉快地领导研讨会,人们充满活力,提供的服务超出了他们的预期。

开一场事件风暴会议,领域专家、产品经理和技术人员等都参与其中,其中包括那些知道要问的问题(以及哪些人很想听听答案)以及知道答案的人,协调人必须使团队保持专注和参与,指导会议进展直到能完成完整的领域模型。

从领域事件开始探索领域,按时间线向前和向后发现领域事件,探索领域事件的起源。某些事件是用户操作的直接后果,因此使用不同颜色的便签区分命令和事件。

● 蓝色便笺表示命令。

● 橙色便笺表示事件。

在便签上写上命令或事件的名称,贴到墙上或白板上,所以需要有足够大的墙面。

如果讨论变得热烈,就需要固定前进的目标方向,以下是一些标准路线和方向。

1)首先,探索子域。每个专家只谈自己最擅长的子域,将领域中的其他部分留给其他人。不同的责任区域可以很好地映射到不同子域中的一部分。

2)其次,探索有界上下文。在讨论过程中,可能会出现一些冲突领域,具有DDD思维模式的开发人员和推动者会对同一术语有不同解释,这可能是在共享的多个同一模型之间划分边界的好时机。

3)然后,注意草绘用户角色。在谈论命令时,对话倾向于指向发出命令的有界上下文和触发动作的人:谁在什么情况下发出这些命令?谁在什么场景下发出这些命令?可以使用黄色便签表示主语角色。

4)最后,识别关键测试场景,因为除了明确定义验收测试之外,没有其他更好的方法来消除歧义。最好有一些图表输出,例如上一节DDD专家Nick Tune推荐的表单就是这样的图表。表格或图形对于给定用户特别有价值,只需将其绘制出来放置在与其关联的命令附近。

虽然名为事件风暴会议,其实参与会议的人根据其习惯可以使用各种方法,包括UML绘图、讲故事式的对话等,对动词敏感的人可以使用发现命令和事件的方式,这些方式的共同点都是按照时间线对动词进行排查发现。

使用事件风暴会议最好的时间是在项目开始时,团队能从一开始形成对领域模型的共同理解。使用事件风暴的另一个高回报时间是项目结束后,用于捕获和分享团队在构建软件过程中学到的知识。

事件风暴的目标是创建和分享对领域模型的共同理解,它不是流程图、设计文档、UML图、部署计划、架构图或任何与实现相关的其他内容的替代品,可以将其视为低保真、临时信息的辐射器,用于与其他人共享和确认领域模型。

复杂领域建模咨询专家Nick Tune总结了事件风暴建模的一些具体技巧。

首先是避免过早进行通用性抽象。例如,人活着干什么?无非吃喝拉撒。这个回答其实已经非常抽象,但是给人感觉却是非常具体,这种抽象其实是一种肤浅的抽象;又如:用户登入系统、浏览商品、下订单、支付、发货,这些事件和组成的流程是通用的电商系统抽象,这种抽象只适合在讲解事件风暴和DDD建模的入门教程中,具体到某家电商公司还是有很多具体特点的。

因此,需要结合具体场景和上下文来进行领域事件提取,例如有的电商公司是先支付后才能发货,有些电商公司还有审核流程,这些都要加入事件风暴会议,否则就沦为泛泛而谈的纸上谈兵会议。

再以招聘网站为例,过早通用无意义的抽象事件有:招聘人员登入系统、创建招聘要求、发布招聘、求职者申请职位、面试安排。

这是非常通用的招聘系统事件和流程,这种抽象没有意义,需要具体化,发现各种分支流程,那么具体化以后的招聘系统事件和流程可能是这样的:外部招聘人员登入系统、分别购买大型招聘计划和轻型招聘计划、根据大型和轻量招聘策略将相应要求发给招聘者、招聘人员拒绝一些要求。

这里引入了招聘计划,这实际是一种业务策略或规则,那么招聘人员根据招聘策略可以进行一些招聘需求的挑选。讨论如果拒绝了会走怎样的流程等问题,这样就逐渐发现了主流程以外的分支流程,整个业务领域就丰富起来了,像树一样,不但有主树干,还有各种分叉树枝,这样的树才是丰富具体的活生生的一棵树。

事件风暴会议不只是用来发现领域事件,更重要的是挖掘领域中的业务策略和业务规则,如果没有这些,这种系统只能判断为是一种简单的CRUD系统。

在事件风暴会议中要有对CRUD的怀疑精神,警惕各种带有CRUD含义的领域事件,具有通用后缀(如Created,Updated,Changed,Changed和Deleted)的事件表示正在使用技术术语来描述领域而不是真实的领域术语。特别是Updated可能是最大的警告信号,它是一个非常笼统的术语,只是意味着已更改。当我们进行事件风暴时,需要试图捕获领域中的丰富性,因此应该追问“为什么更新了某些内容?”。

对CRUD命名的怀疑不仅会帮助开发更丰富的领域词汇表,改善技术人员与领域专家之间的协作,而且还将帮助发现领域内的其他流程。了解更改的每个不同原因,将发现许多领域中的见解。例如,不要用“价格更改”这样的领域事件,而是与领域专家进一步讨论,追问为什么价格会更改。你可能会发现的原因有“价格打折了”“季末促销”“圣诞促销”等,使用这些业务原因来命名领域事件。其实更改或更新只是一种状态变更,而事件建模应该不是针对状态。

类似CRUD事件的还有:表单提交、按钮点按、数据库保存。这些事件也只是领域事件的技术事件,并不是真正的领域事件。表单提交(Form Submitted)可以使用带有业务含义的词语替代:工作提交;订单提交;帖子提交。虽然这些业务数据确实表现为一个通用的表单,但是使用技术表单(HTML)术语超出了业务领域的边界。

表单中的每个字段数据最好在事件风暴会议中列出,列出这些字段的各种可能数据值,了解这些不同值的含义与差异,将具有重大差异的值建模为不同的流程。例如,填写表单时,有一个性别栏,如果是男,那么表单的其他填写项目就会有一些不同,这只是业务数据值对表单界面造成的轻微影响,而更重大的影响可能是:当前表单提交后,男或女不同选项导致出现不同的下一个表单,这实际已经是不同的流程走向了。从这种细节中发现新的分支流程,丰富业务领域。

当表单提交以后,通常是表单的数据创建了、更新了或删除了,这些CRUD事件只是肤浅的抽象事件,特别是某数据更新了只是一种状态更新。要培养对状态更新的敏感性,状态机几乎遍布每个领域。正在建模的概念会经历其不同的生命周期阶段,这些不同阶段使用“状态”一词来表达,虽然不能直接针对状态进行建模,但是可以通过发现状态机来发现领域事件,进而能发现领域中的上下文边界(也会成为微服务的边界)。状态机是边界的凝聚核心,领域事件是边界的边缘,与外界交互的事物。

当然,不是所有领域事件都意味着状态改变,有一种特殊的事件可以识别状态机状态之间的转换,称为“关键事件”。关键事件是领域边界的有力指标。

在物联网设备领域,可能会发生以下关键事件:设备制造、已配置设备、已安装设备和已激活设备。尽管在整个过程中发生了数以百计的事件,但是这些是指示重大变化的关键业务事件。

在后面事件溯源建模章节中会看到,通过状态切换的表面现象发现其原因:领域事件。当然,首先要培养状态敏感性,这一点主要是从上下文等场景去了解:当前这个上下文主要关心的是什么数据变化?例如,购物车场景和订单场景有什么不同?购物车主要是对购物车里的商品进行挑选,这时引起的状态变化应该是购物条目明细:某个商品A购买了五件;某个商品B购买了三件等等。但同样是这些购买商品明细,到了下订单场景,这些购买明细就变成了订单明细,订单明细就不能再进行加入或去除等变更了,这时状态就不是订单的条目明细,而是整个订单的确认、支付和发货等变更。状态目标明确了,领域事件就会跃然纸上。

在事件风暴建模中,需要多从表面的现象变化追问“如果..会..”,否则会陷入很轻松、很习惯、很确定的思路陷阱中去,在这些舒适环境中就不会有什么新的发现。大哲学家罗素曾经阐述哲学有什么用处。哲学不像科学那样能够带来物质利益,也不能回答大多数人的问题,但是哲学提供各种可能答案,拓展各种思路,经历各种不同角度的思考。这些答案没有一种是标准答案,问题本身和多个答案或没有答案本身已经证明其价值:不确定性。不确定性是好奇心的代名词,只有不确定才会有探索冲动,才会激起好奇心。事件风暴会议本身就是一种探索问题的各种答案和答案是否存在的方式,突破舒适区,追问原因,拥抱不确定性,用好奇心驱使创新。

通过“如果..会..”的追问,会发现领域中的很多边缘情况。查看墙上的每个事件并询问:“如果发生停电怎么办?”“如果拒绝客户的信用卡怎么办?”“如果仓库发生火灾怎么办?”,对该领域具有浓厚的好奇心,将带领与会者提出各种探索性和挑战性的问题,从而获得有关该领域的更多见解。

这种哲学式的批判精神和怀疑精神会让整个参与会议的团队陷入迷茫或混乱中,混乱的探索是事件风暴的标志之一,整个团队都在集体中集思广益。每个人都可以将自己的想法、假设和疑虑用便签贴在墙上。混乱的探索对于事件风暴提供的许多发现至关重要,但是,有时精力不足,团队感到困惑,或者由于许多可能的原因而没有取得进展。这时需要切换到单线程模式,只让一个人贴标签,其他人不再贴标签了,顺着这个人的思路一起讨论,一次添加一个事件,以确保小组在继续之前达成共识。如果小组陷入僵局,请做笔记并稍后返回。在简短的单线程会话之后,该小组将有许多想法和他们想更详细地建模的其他方案,这时又是切换回混沌模式的好时机。

2.4.4 实例解析:一个典型的事件风暴建模议程

以预订电影票为案例,说明一个典型的事件风暴建模议程。

第一步:头脑风暴,参会者写出所有他们能够想到的事件。对于订票领域最初想到的可能是:选电影、选座位和付钱,需要将事件写到橙色的便签上,并且用领域事件的格式,名词在前、动词在后,如FilmPicked、SeatPicked、PaymentSucceeded,如图2-10所示。

图2-10 订票相关事件

这是最初可能想到的几个事件,非常粗糙,只是与用户相关的事件,领域事件应该是整个领域里发生的事件。

有人提出需要关注的业务规则是:如果没有支付成功,选定的座位需要释放。如何使用领域事件表达这种规则?这时需要按照时间线深入流程。

第二步:按时间线组织这些事件,创建一个由这些事件组成的合理故事。将他们排成一行,每个人都回顾这个时间线,以确定其中有意义的事件,如图2-11所示。

图2-11 按时间线排序事件

第三步:加入界面和命令。对于那些视觉学习者,可以列出界面,包括其中每个字段,这样的系统蓝图具有用户的角度,便于审视信息的来源和目的地,如图2-12所示。

在这张图中,加入了界面元素,还有用户操作该界面产生的动作命令,例如显示影片列表,用户会挑选一部影片,命令是PickFilm,将来的系统接收到该命令后,产生结果是事件FilmPicked,然后,用户根据显示空位的界面,发出挑选一个座位的命令PickSeat,系统接受后,产生事件SeatPicked,最后,用户根据支付页面发出支付命令Pay,支付成功后发出PaymentSucceeded事件。支付可能成功或失败,如果支付失败,则需要放弃选择的座位。

图2-12 订票系统的命令和事件

第四步:加入流程的因果关系,也就是将命令和事件串联起来,如图2-13所示。

图2-13 订票系统命令转事件

当加入逻辑或时间上的因果关系以后,就会发现逻辑漏洞,进而挖掘出业务规则。当座位选择以后,发出SeatPicked事件,然后用户发出支付命令,支付失败以后怎么呢?加入支付失败事件,如图2-14所示。

图2-14 支付失败处理

支付失败事件PaymentFailed发出后,会自动转换释放座位的命令ReleaseSeat,系统执行后,产生SeatReleased事件表示该座位已经释放。

第五步:发现有界上下文。将领域中重要的命令和事件使用有颜色便签贴在墙上以后,基本能对问题空间中的领域知识有一定统一认识。那么现在希望划分有界上下文,这样才能分成不同团队分别干活。从图中大概发现可能分三个有界上下文:挑选电影环节‘选座位环节和支付环节,如图2-15所示。

图2-15 有界上下文划分

但是支付失败后,释放空位这对命令和事件是在哪里发生呢?是一个单独的有界上下文吗?释放座位的目的是将空位让给别人,别人才可以挑选这些空位,这些行为在逻辑上是一致的,释放座位和挑选座位应该属于同一个上下文,如图2-16所示。

图2-16 事件归属有界上下文

这样,这个领域划分为三个不同的有界上下文:选片上下文;座位管理上下文;支付上下文。这三个上下文的名称取决于统一术语,利用这次头脑风暴会议正好统一相关的术语,一旦确定,以后就不要用其他名称,比如不能再称为“挑选电影上下文”“挑选座位上下文”等。

在这三个上下文中哪个是核心子域呢?其中,座位管理这个有界上下文属于核心子域,它有比较复杂的座位加锁和释放逻辑,如果用户选择了座位后不进行支付怎么办?是不是这里还要加上座位锁定Timeout事件?座位被某个用户锁定20分钟,这20分钟内没有支付,系统将自动执行释放座位命令,同时解锁座位。

座位管理上下文和支付上下文之间的关系比较密切,座位选择好后才能支付,支付失败后必须释放座位,这两者之间通过什么上下文关系联系比较好?这也是事件风暴会议需要关注和解决的,这同时也决定了两个团队之间的耦合程度。主要是在API的开放主机和异步消息的发布/订阅两种方式中选择。由于座位锁有20分钟计时(Timeout),所以座位锁的操作对实时同步性要求并不是很高,因此选择异步的发布/订阅方式可能比较合适。

事件风暴是一种灵活的研讨会格式,用于协作探索复杂的业务领域,可以直接进入复杂核心并快速轻松地学习它。它具有不同的格式,可以分享全局视觉和知识,了解特定流程并直接进入解决方案领域。

会议准备很简单,需要找到足够大的墙来放纸张,邀请合适的人(有知识的人和那些实施知识的人),并给每个人提供相同几个特殊颜色的黏性便笺和笔。会议间环境非常重要,需要足够的墙面空间,新鲜空气和奶茶、咖啡、肯德基等各种零食,缺氧或缺糖对大脑都是致命的,椅子要少,不要坐着讨论,而是站着讨论,如图2-17所示。

讨论的方式不一定是聚在一起争论,可以分成小组各自学习讨论,或者面对大墙集体讨论,如图2-18所示。

图2-17 事件风暴会议现场示例1

图2-18 事件风暴会议现场示例2

这里介绍一些用于事件建模的工具

Kubel是一个用于探索有界上下文的实验性开源工具。将以下自行车共享案例的内容复制到https://robertreppel.github.io/kubel/中:

Kubel会生成相应的可拖动的词汇,将词汇表拖动到表示不同有界上下文的组中,这样可以帮助权衡评估哪些词汇能够汇聚到一个有界上下文,如图2-19所示。

其他事件风暴会议的绘图工具有miro和lucidchart等,进一步了解事件风暴建模可以访问以下资源。

图2-19 Kubel界面

● 经验分享:在金融企业中实施领域驱动设计的敏捷实践(https://www.jdon.com/52728)。

● 事件风暴专题(https://www.jdon.com/tags/45214)。