DDD 系列:事件风暴入门
上一篇文章中我们学习了通用语言和上下文映射知道了同一个限界上下文内要尽量使用统一的业务词汇不同上下文之间也要明确协作关系。不过业务语言和边界并不是凭空设计出来的。真正开始做领域建模时我们还需要和产品、业务、测试以及研发一起把一段需求从头到尾梳理一遍。这时候就会用到事件风暴。**事件风暴是一种以业务事件为线索通过多人协作梳理业务流程、业务规则和领域边界的建模方法。**它不是单纯开会也不是把需求文档换成便利贴而是让参与者围绕同一条业务流程持续讨论直到关键概念和业务结果基本说清楚。一、为什么需要事件风暴我们平时接到一个需求往往是产品先整理需求项目经理再拆任务开发根据需求文档写代码测试最后根据页面和接口验证功能。这个流程本身没有问题但每次转述都可能发生一次理解偏差。比如产品说“用户下单后可以取消订单”开发可能理解成修改订单状态测试可能只验证未支付订单而业务人员还认为已支付订单也应该支持退款。最后大家使用的是同一个词脑子里想的却不是同一个流程。**事件风暴要解决的核心问题就是把需求讨论从“描述功能”拉回到“发生了什么业务事实”。**大家不先讨论表结构和接口而是先回答业务过程中发生了哪些事件是谁触发的事件发生后还要做什么有哪些规则会影响下一步这也和前面讲的通用语言、限界上下文有关。事件风暴过程中沉淀下来的业务词汇可以作为通用语言的输入事件和动作的聚集关系也能帮助我们进一步识别业务边界。二、事件风暴的基本元素事件风暴通常使用不同颜色的便利贴表示不同类型的业务信息。颜色不是重点重点是每种卡片都要表达一种明确的业务含义。实际使用时团队可以约定自己的颜色只要整场讨论保持一致即可。1. 领域事件领域事件表示已经发生的业务事实通常使用过去式表达例如订单已提交购车合同已签署库存已锁定退款已完成。事件应该描述“已经发生了什么”而不是“准备做什么”。“提交订单”是动作“订单已提交”才是事件。领域事件是事件风暴的中心。我们先把业务结果列出来再围绕这些结果反推动作、角色和后续规则。2. 命令或动作命令表示希望系统执行的动作它通常会导致某个领域事件产生例如用户提交订单 - 订单已提交 用户签署合同 - 购车合同已签署 系统锁定库存 - 库存已锁定命令关注的是“谁要求系统做什么”事件关注的是“做完之后发生了什么”。这两个概念不要混在一起否则后面分析流程时很容易把待办事项和业务事实混为一谈。3. 角色或执行者角色是发起命令的对象可以是用户、运营人员、系统任务也可以是外部系统。例如用户点击提交按钮用户就是执行者定时任务关闭超时订单定时任务就是执行者支付平台通知支付成功支付平台就是外部执行者。4. 策略或业务规则策略表示事件发生后根据某些条件决定下一步动作的业务规则。例如订单支付成功后如果订单包含分期商品就创建分期计划如果订单包含优惠券就记录优惠券核销结果。策略不只是技术上的if/else。它表达的是业务在什么条件下应该走哪条路径后续可以继续拆成领域逻辑、应用服务编排或者外部系统协作。5. 读模型事件发生后用户或者其他角色通常还需要看到某些结果这部分展示数据可以先作为读模型记录下来。例如“购车合同已签署”事件产生后订单详情页需要展示合同编号、签署时间和合同状态这些就是用户关心的查询结果。读模型的重点是服务查询和展示不要因为看到“模型”两个字就马上把它和后面战术设计中的实体、值对象混在一起。本文这里只把它作为事件风暴中的分析结果来使用。6. 外部系统有些事件或动作并不属于当前系统而是由外部系统参与完成。例如当前系统发起扣款支付系统返回扣款结果当前系统通知合同平台保存合同合同平台再返回签署结果。这些外部系统要单独标记出来避免把别人的职责误认为当前领域内部逻辑。事件风暴的最简链路可以理解为角色 - 命令 - 领域事件 - 后续策略/命令 - 新的领域事件下面这张图把常用的卡片关系放在了一起重点看事件如何成为流程的中心以及规则、读模型和外部系统分别从哪里接入。三、一次事件风暴怎么开展事件风暴通常以 Workshop 的形式开展。参与者不一定越多越好关键是要覆盖能够说明业务、验证业务和实现业务的人。1. 准备参与人员一般需要包括熟悉业务流程的产品经理或业务人员负责系统设计的架构师或技术负责人参与开发的研发人员负责验证流程的测试人员必要时邀请运营、财务、客服或外部系统接口负责人。这里需要注意事件风暴不是开发人员自己画图。如果真正了解业务的人没有参加最后很可能只是把开发人员的猜测贴到了墙上。2. 准备场地和材料可以准备一面白板、一块大画布或者在线协作工具再准备不同颜色的便利贴。建议提前约定颜色含义例如卡片类型示例作用事件订单已支付表示已经发生的业务事实命令支付订单表示希望系统执行的动作角色用户、定时任务表示动作的发起者策略支付成功后生成发票表示条件和后续规则读模型订单详情表示需要查询或展示的数据外部系统支付平台表示当前系统之外的参与者颜色只是辅助记忆卡片上的文字才是最终要讨论的内容。3. 先确定业务目标开始时不要急着讨论数据库表和接口字段先把这次要梳理的业务范围说清楚。例如本次只梳理“分期购车订单从提交到合同签署”的流程那么支付对账、售后退款等后续流程可以先标记为边界外内容避免一次会议把所有问题都混在一起。4. 按时间顺序铺设事件通常由产品或业务人员先讲一遍完整流程参与者把听到的业务结果写成事件再按照大致发生顺序从左到右排列。这一步先求完整不要一开始就纠结每个词是否完美。遇到不确定的地方可以先标记后面集中讨论。5. 围绕事件反推上下游对每个事件依次追问这个事件之前发生了什么命令是谁发起了这个命令命令执行需要满足什么前提事件发生后哪些策略会被触发后续动作由当前系统完成还是需要外部系统参与用户或其他系统需要看到什么结果如果大家对同一个事件出现不同理解不要直接选一个“看起来合理”的答案而是把差异记录下来回到业务规则本身继续确认。6. 记录争议和未决问题事件风暴的结果不是一次会议就能得到最终答案。对于暂时无法确认的问题可以使用单独的标记记录下来例如“待业务确认”“需要查看历史数据”“需要和支付系统负责人确认”。**没有确认的业务规则不要悄悄变成代码中的默认逻辑。**这类问题如果不记录后面很容易变成“开发当时就是这么理解的”再想追溯就比较麻烦了。四、案例梳理分期购车订单下面以一个简化的分期购车订单为例只演示事件风暴的思考方式不展开完整的金融业务规则。假设业务流程是用户提交购车订单订单审核通过后签署购车合同合同签署完成后启动金融流程。1. 先列出业务事件按照时间顺序可以先得到这些事件订单已提交 订单已审核 购车合同已签署 金融任务已启动这些内容描述的是业务已经发生的结果不是页面按钮也不是数据库状态值。2. 以“购车合同已签署”为中心反推我们选中“购车合同已签署”这个事件开始逐步分析产生这个事件的命令是“签署购车合同”命令的执行者可能是用户也可能是合同签署系统需要根据真实流程确认签署合同之前订单必须已经通过审核审核通过是这一步的业务前提事件发生后合同平台需要保存签署结果这是一个外部系统协作如果订单是分期订单系统还要触发“启动金融任务”命令订单详情页需要展示合同状态、签署时间和合同信息这些属于读模型。用文字表示流程大致如下订单已审核 | v 用户签署购车合同 | v 购车合同已签署 |----------------------- 合同平台保存签署结果 | -- 分期订单 ------------ 启动金融任务 | ----------------------- 更新订单详情读模型下面的案例图把这条链路和判断点画了出来。图中的“分期订单”是业务策略不是简单的技术分支它说明事件发生后不同订单类型会触发不同的后续动作。3. 从事件聚集关系识别业务模型继续观察上面的事件和命令会发现“订单已提交”“订单已审核”“购车合同已签署”都围绕订单展开命令的前提、状态变化和后续业务也主要由订单承载。因此我们可以初步把订单识别为一个业务聚集点。这里的“初步”很重要事件风暴得到的是建模线索不是直接生成最终 Java 类和数据库表。后续还需要结合业务边界、数据一致性要求和团队对模型的理解继续确认。**不要看到几个事件都和订单有关就马上把所有相关对象都塞进一个聚合。**聚合大小和边界属于后面战术设计要继续讨论的问题。五、事件风暴和后续 DDD 设计的关系事件风暴结束后通常可以沉淀出几类结果一组已经统一过含义的业务事件和业务术语一条相对完整的业务流程和关键分支参与流程的角色、外部系统和业务规则可能的业务聚集点和领域边界仍然没有确认的业务问题。这些结果可以继续支撑后面的战略设计和战术设计用事件和术语补充通用语言根据业务流程和职责边界识别限界上下文结合一致性要求进一步分析实体、值对象、聚合和聚合根再根据模型结果设计代码结构和数据结构。这里要分清楚一个边界**事件风暴是分析和建模方法不是自动生成代码的工具。**它能帮助我们把业务讨论变得具体但最终模型仍然需要结合实际需求和实现成本反复调整。六、总结本文介绍了事件风暴的基本概念和一套可以落地执行的讨论流程。简单总结一下事件是核心先描述已经发生的业务事实命令表示要做什么角色表示谁来做策略表示事件发生后的业务规则和后续动作读模型表示用户或其他系统需要看到的结果外部系统要单独标记避免把外部职责混入当前系统通过事件、动作和规则的聚集关系可以得到后续领域建模的线索。事件风暴并不要求一次会议把所有模型都定死。真正重要的是让业务人员和研发人员围绕同一条流程把含义说清楚把争议和未知记录下来。下一篇文章我们进入 DDD 的战术设计开始学习实体和值对象。到这里DDD 的战略设计部分就暂时告一段落。