前置知识
你需要知道什么是DDD?DDD的一些基本概念,比如:
- 实体和值对象是什么?
- 聚合是什么?
- 领域是什么?
- 限界上下文是什么?
- 上下文映射是什么?
学习目的是什么?
- 事件风暴是什么?探讨它怎么诞生的?
- 为什么要用事件风暴?
- 怎么做?
- 需要注意什么?
是什么?
事件风暴是一场发现事件模型的头脑风暴会议,通过领域专家、产品经理和技术人员共同头碰头的群策群力,以事件模型为主要线索,发现业务系统中发生的代表重要事实的重要事件。
什么是事件?
Event As Fact事件即事实。即在业务领域中那些已经发生的事件就是事实。
过去已经发生的事件已经成为了事实就不会再更改,因此信息管理系统就可以将这些事实以信息的形式存储到数据库中,即信息就是一组事实。
小白:换句话说增删改数据库就意味着发生了事实,产生了事件?
小黑:是这样的。说到底,一个信息管理系统的作用就是存储这些事实。对于这些事实进行管理与追踪,进而起到提高工作效率的作用。
小黑:分析一个业务管理系统的需求就是准确的,抓住业务进行过程中那些需要存储的关键事实。并围绕这些事实进行分析设计、领域建模。这就是事件风暴的精髓。
为什么?
一句话:可以快速分析和分解复杂的业务领域,完成领域建模。
事件是捕捉上下文语言中的重要抓手,通过事件建模能够掌握事件这个事实发生的上下文背景知识,从而划定上下文的边界,形成DDD界限上下文。
- 充实通用语言,让领域专家和开发人员能够更好的沟通交流
- 剖析复杂业务,了解业务详情和业务流程
- 构建领域模型(领域事件、聚合和限界上下文等),为后期战术设计做铺垫
除了领域专家,事件风暴的其他参与者可以是 DDD 专家、架构师、产品经理、项目经理、开发人员、测试人员和UI设计师 等项目团队成员。
怎么使用事件风暴?
总结:一个
User根据看到的Read Model/Information,决定对External System或者Aggregate执行一个Command,进而产生了某种Domain Event。此
Domain Event可能触发了某种Policy,Policy可能又对External System或者Aggregate执行一个Command。此
Domain Event也可能会导致Read Model/Information发生变化,从而给User提供更多信息以进行其他操作。
找个案例
最怕静悄悄的新人!
小黑:这里我强调下。在开事件风暴的时候,很多新人比较拘谨,害怕丢脸。我想说不要有太多的顾虑,只要你能想到的,便往上面贴。 小黑:最怕新人不懂装懂,没讨论就偷偷往项目里添加自己的臆想,等出事后,小锅领导背,大锅让你滚
小黑:按照上图的步骤,我们先开始罗列领域事件。
小白:嗯,可以说说事件罗列过程中有什么窍诀吗?整个过程是怎样的?
小黑:这个过程主要就是举出一个事件,然后考虑事件的起因和事件的结果。
小黑:比如
订单已创建,如果我们要找前因,就需要找为什么会达到订单已创建这个程度?然后再找到后果,就是订单已创建后,还会产生其他别的什么事件吗?
小白:让我按照因果律的规律。订单已创建如果作为果的话,那么输入的因到底是什么呢?
小黑:你的这种方法确实不错,可以考虑因果律也可以考虑输入输出。
小白:嗯,订单已创建,说明已经有了订单,这些信息,那么这些信息的来源是哪里呢?最明显的地方应该就是这些信息是从购物车里面来的,或者是由用户选择而来的。
小白:那么就会有这么一个事件,就是
商品已加入购物车,还有就是商品已创建、库存已增加等等。小黑:你说的这些都没有问题,但是你少了一个
订单已验证事件。但这是能够理解的,因为我们的领域事件罗列并不完整,所以不能够发现这中间还有一个订单已验证。
小黑:如果你看过
订单已创建后续的事件的话,你会发现他会有一个库存已锁定。你会发现,如果订单这个数据不经过验证,直接被创建,那么在库存已锁定的过程中会报错,那就意味着这个订单应该事先验证一下是否能够被创建?小黑:接着我们考虑订单已创建完毕后,将会有哪一些领域事件的产生。
小黑:买家创建了订单,就代表买家和卖家形成了一个初步的契约。在这个契约下订单超时,订单就会被取消。同时,为了防止订单中的商品在订单未过期时间内被其他用户购买,则会锁定库存。所以这里需要一个库存已锁定。
小黑:紧接着,锁定库存后,下一步,用户应该是输入密码支付,所以这里还缺少一个
支付已完成,一般我们这里使用第三方支付系统,比如说支付宝或微信。等待第三方支付信息系统通知电商系统后,才会有订单已支付。小白:等一下,等一下库存已锁定,也有可能锁定失败吧?
小黑:前面说过了,如果你进行了订单验证,正常情况下,库存已锁定事件并不会失败,但是它会超时,它会随着未支付订单的超时而解除库存锁定状态。
小白:还有就是订单已支付,万一用户支付失败了呢,或者用户取消支付了呢?
小黑:在领域事件罗列的过程中,我们不应该考虑的特别细致。当然,如果实在不行的话,你可以用紫色标签做一个提醒。
小白:还有个问题,就是订单超时了,那么肯定会有一个订单已取消这个事情吧?你在上图,并没有把它罗列出来,这个事情应该比较重要。甚至还有库存已释放,还有已支付订单的退款等等
小黑:前面我已经说过了,在罗列事件的过程中,我们先把一条路走通,然后再尝试分析这条路的分岔口上又会有哪一些领域事件。
小白:那接下来还需要做什么事情呢?
小黑:接下来我们可以考虑为每个事件添加命令
Command和角色User/Actor。小白:对了,你是否有考虑在购买商品的过程中还有优惠券的问题呢?
小黑:对,这是中途需要插入的一个议题,也就是我们把命令和用户添加完毕之后,在分析的过程中,可能还会有新的灵异事件产生,它并不是一蹴而就的。但是我们可以按照从左往右的时间顺序添加到对应的领域事件上方或下方。
小黑:比如下图标记1的位置。我们将优惠券的信息添加到对应领域的上方
小黑:在上图三这个位置会有一个外部支付系统的添加。其实还有很多,不仅仅是外部系统会产生事情,还有规则。
小白:那么接下来还要做什么呢?
小黑:接下来,我们需要按照时间划分阶段,就像上图这样的,我们可以划分为购买前、购买中、购买后三个时间段。在完成购买之后,发货与运货之间亦存在明显的时间间隔,可以继续拆分。于是,关键时间点将整个事件流分割为四个子领域边界:商品、订单、库存、物流。分割了边界后的模型如下图所示:
小白:对了,为什么上面的事件风暴
Command啊? 这是为什么? 小黑:那是Big-Picture Event Storming,该模式主要的目的在于洞悉业务主题框架,所以不需要Command,而且Command通常跟Event一一对应,特殊情况不写也没什么问题(比如:提交订单对应订单已创建)
小白:那么接下来要干什么?
小黑:接下来是添加
Command和聚合的时候了小黑:买家执行决策命令“添加商品到购物车”,需要获得读模型“商品”。由于事件修改了购物车的内容,意味着决策命令将通过聚合“购物车”发布事件
小黑:然后考虑后置事件“订单已提交”。同样由买家执行决策命令“提交订单”,需要获得包括购物车和客户的前置信息。
小黑:购物车中被选中的购物项会进入订单,要提交订单并验证订单,需要提供客户的联系信息、配送地址等内容。
小黑:当前事件改变了订单的状态,因此,“订单”就是我们要寻找的聚合对象。
小黑:在提交订单时,需要验证订单是否有效,因此考虑引入“订单验证规则”策略对象。该策略对象本身会引发“订单已验证”事件,但该事件属于管理者并不关心的中间内部事件,故而没有呈现在事件风暴的模型中
小黑:最终我们得到如下作品
小白:按照你这么说,我觉得事件风暴过于复杂了。
小白:从后续DDD项目落地的结果来看,
Command通常使用cqrs读写分离方式,其对标Controller的每一个方法,既然如此,为什么不直接丢弃领域事件,而直接使用Command呢?小黑:这里我们需要注意一个问题,命令和领域事件并不是一一对应的。有的时候用户发起一个命令,会产生多个事件,有的时候却一个事件都不会产生。
小黑:比如在spring security中,用户登录就不是在
controller。用户登录功能在spring security自带的代码中,用户可以去自定义这个登录代码。
参考链接:[063 案例 订单核心流程的事件风暴 (lianglianglee.com)](learn.lianglianglee.com/专栏/领域驱动设计实践… 案例 订单核心流程的事件风暴.md)