事件风暴将掀起一场新革命

板桥里人

  事件风暴建模是一种基于领域驱动设计DDD的业务建模方式,它能够直接分析动态业务流程,克服以往静态结构分析方法的局限性。或将引发一场新的分析方法变革。

     所谓静态结构分析方法,主要表现为使用数据表结构来表达业务需求,虽然也有使用UML等面向对象的分析设计方法,但人们的分析思维还是拘泥于静态类图分析,例如DDD分析结果主要以产出类图为主、顺序图或状态图等动态分析往往被忽视。

     静态结构分析方法的优势在于发现业务领域中的高聚合,建立一种高凝聚低关联的结构,然而如何通过这种静态结构去实现动态的业务流程功能却为被忽视了。由于这种结构分析方法与技术流向趋势常常发生割裂,即便有设计良好的组件结构,却面临无法实现业务功能的尴尬,这也是DDD在很多项目中遭遇搁浅的主要原因。

那么,到底是哪儿出问题了呢?是DDD不行,还是我们的分析方法有所局限呢?

主要问题出在我们使用静态方法去分析动态事物,是方法论出现了误用,由此,业界提出了一种全新的动态业务建模方法,是从“事件”这个动态角度进行建模。而且,事件建模还是一种全新的动态思维方式,代表思维方向的转变,是由“静”到“动”的转变。

当然,如果我们习惯了传统的静态分析方法,思维方法就很难切换到动态事件这个层面上,因为使用静态数据模拟动态流程可能变成一种设计直觉,使用数据表记录当前的业务状态,然后记录业务不同状态以实现对流程进行跟踪。这种方式实际是将一个动态的流程活生生切割成不同静态的状态,这是我们很多人多年来根本未意识到的,即便意识到也认为是理所当然的。

这种静态结构方法在中小型系统的分析设计中不会有问题,但是如果用这种方法去分析设计一个复杂大型系统,大量间接的模拟表达会造成系统过于复杂晦涩,如果一个程序员在未完全掌握状态切换规则情况下,却更改了状态数据,就会影响一大片流程功能,其结果是灾难性的。

既然很难从状态思维切换到事件思维,那么有什么好办法能够简化这种切换呢?个人的深切体会是:  积累事件驱动系统的分析设计开发经验,在这个过程中不断促成自己反思与提升。你会猛然在某一天醒悟:状态和事件其实是有内在因果关系的,状态为什么会发生变化,那是因为发生了事件,过去我们是把状态作为主角,事件才是背后真正的英雄啊。比如,你的手机正在处于播放音乐状态,那是因为你之前按了播放键,才发生了播放事件,才进入播放状态,如果你再按暂停键,触发了暂停事件,手机就进入音乐暂停状态,当然,如果这时候来了意外的事件,如有电话进来,那么手机也会进入音乐暂停状态,会播放来电铃声。手机的这些状态之所以能够切换,本质是由于动作事件的发生。

事件建模本质就是要抓住一系列事件动作,寻找贯穿业务领域中的事件流向。这是一种动态建模方式,这种动态方式能够直截了当地反映业务流程,无需借助状态来间接表达。

一个复杂的业务流程等同于多个事件组成的有序集合,比如A调用B再调用C以完成一个预订流程,那么可以认为是A发出事件,通过消息触发B,B再发出事件,通过消息触发C。因此,这样一个预订流程是由三个事件依次顺序组成的有序集合,这是多么直接和简单啊。

那么为什么现在才会有事件建模这种想法呢?

过去几年来,技术领域中的消息系统已经呈现突飞猛进的发展,从最早J2EE的JMS到各种MQ产品直至Apache Kafka,这些层出不穷的消息产品其实提供了一种一致的编程模型:发布者-订阅者(pub-sub)模型。

发布者-订阅者模型其实代表对象之间调用的新模式,一般情况下,对象之间是直接通过方法进行调用,随着分布式架构和微服务的普及,对象之间跨网络调用变得非常普遍,最初是通过远程方法调用RPC实现,RPC是一种同步调用方式,虽然看上去很快,但是一旦遭遇网络繁忙就会发生堵塞,而异步调用则是一种对网络容错的新型调用方法,发布者-订阅者模型由此应运而生,逐步替代RPC,成为分布式系统中基本通讯方式。

既然发布者-订阅者模型已经发展成为一种默认的异步通讯方式,而我们业务领域中存在大量跨流程调用,因此,业务复杂流程调用正好可以借助多个发布者-订阅者模型叠加实现,其实发布者-订阅者模型非常符合UML中顺序图实现,顺序图中默认模块之间调用是异步方式,以往我们总是使用对象方法同步实现,这可能是缘于过去消息技术系统不发达而采取的一种无奈变通,如今随着网络质量的提高,分布式系统的普及,现在顺序图的实现完全可以使用发布者-订阅者模型完成了。

时至今日,技术发展已经赶上了几十年前的超前设计思路。发布者-订阅者这种技术模型自然会迅速渗透进业务分析设计领域,而事件则可以说是发布者-订阅者模型的抽象体现,当我们使用多个事件来表达复杂业务流程时,事件之间的联系是通过发布者-订阅者模型实现先后流程节点连接,从另外一个角度看,发布者-订阅者模型实际被隐含在事件流背后了。

那么,事件建模为什么称为事件风暴建模呢?这是取自于头脑风暴,意味着参与者需要在一起通过头脑风暴才能实现较为成功的事件建模,

事件风暴建模从组织形式上看很简单,相关专家和技术人员集中到一个会议室,在一面墙上贴上白纸,然后使用不同颜色的便签表达不同事件,以此表达各种业务流程,事件风暴的价值是沟通,而不仅仅是粘贴在墙上的便条。

事件建模不是对所有事件都进行关注建模,而只是关注领域事件。

Martin Fowler对领域事件的定义是:“重要的事件肯定会在系统其它地方引起反应,因此理解为什么会有这些反应同样也很重要。”

领域事件的重要特征是能够引起反应,不是所有事件都值得我们关注或记录,最引人注目的是那些引起反应的事件。由此,领域事件将事件与事件反应或者称事件响应联系起来了,这种方式符合我们前面讨论的发布者-订阅者(pub-sub)模型,所以,事件风暴不只是找出孤立的一个事件,而是要找出“事件/响应”这样的组合,唯有如此,我们才能拼凑出一个事件发生的序列因果集合,从而完整地表达业务流程。

如果说,事件与状态是一种因果关系,那么事件与响应之间也是一种因果关系,事件风暴建模就是将事件与状态的静态因果逻辑思考上升到事件与响应的动态因果逻辑思考。

当我们使用事件与响应这样的动态因果关系来描述业务时,一切变得不一样了,比如需求中有一个功能要求:“当一个新用户账号被创建以后,系统需要发送一份邮件确认”,在这条需求中,账号被创建是一种事件,而发送邮件确认则是一种响应。

通过事件风暴建模以后,我们有了各种事件流,而且随着业务需求扩展,事件流数量会增加很多,这时候,领域驱动设计DDD的结构化方法才会派上用场,它提供一种约束方法将这些事件流归集到结构化模块中,领域驱动设计提供了围绕事件自然地界定上下文边界的方法,并且界定了哪些事件需要跨越边界流动。

如果把事件流比喻成流动的河水,那么DDD就是找出河水流过的地域,比如河水是从哪里出发,流过了哪些山川,最后归集于哪里入海?

因此,DDD中聚合概念在逻辑上实际是将各种命令、事件响应进行了分类分组。一个书籍聚合体聚合了与书籍有关的各种事件和响应,而一个账户聚合体聚合了与账户有关的各种事件和响应。

聚合是将结构化设计带入了系统,能够实现结构上的分离关注,虽然它与业务流程没有太大关系,但是它提供了必要实现手段,比如你可以考虑,如果我改变了货运系统代码,会影响支付吗?如果它们两者不是聚合在一起,那就当然不会。

同时,DDD中有界上下文对于我们事件建模也是有作用的,下文可以订阅上文中重要事件,通过事件将上下文串联起来。比如当一个系统划分为支付和货运两个上下文模块时,可以减少相互影响,比如支付模块只负责发射支付事件,货运模块只要简单订阅这些事件,实现相关货运业务即可。同一个概念在不同上下文中意义也可能是不同,比如“确认”在支付上下文和货运上下文就是不同含义,我们需要将其明确成“货运确认事件”和“支付确认事件”就能避免混淆。

通过定义上下文,能够理解系统子领域之间的交互,货运系统可以是一个老系统,而支付系统可以使用Actor模型实现的高并发实时模块,它们彼此之间通过事件消息通讯。

好了,我们讲了这么多,涉及不少抽象概念,如果你是一个老谋深算的分析老手,可能会有所触动;如果你是一个新手,也不要着急,也许经过大型系统失败历练才会有所领悟;如果你正在处于痛苦的救火过程中,比如迫于数据量或访问压力在进行分库分表,需要各种定时任务进行批量处理,需要在夜间核心业务系统空闲时计算各种数据报表,不妨静下来想想,也许换一种角度看待系统,从事件风暴角度重新分析设计你的系统,使用Reactive响应式架构重新实现你的系统,也许能够一揽子解决这些问题。那些后台运行的分布式定时任务、批处理任务等等,如果使用Reactive则可以变成微实时的任务系统,而通常系统夜间忙碌生成各种报表,使用Reactive以后就可能变成实时动态输出的仪表板了。

风暴来临,游戏已经改变。