目的
- 帮助理解需求
- 系统有哪些功能
- 这些功能由什么角色操作
- 操作后会产生什么效果
- 帮助识别领域对象
开始前的准备
对主业务流程有自己的理解
事件风暴的主要过程
事件风暴的第一步:识别领域事件
- 所谓领域事件,就是在业务过程中,业务人员要关注的那些已经发生的事儿(比方说,对于电子商务系统,订单已提交、商品已签收等等,都是领域事件)。
- 实际上,领域事件表示的是,业务流程中每个步骤引发的结果。
- 事件风暴的作者认为,从结果入手来梳理需求,比从操作入手,更容易把业务想清楚。
- 事件风暴中的“事件”两个字就来源于领域事件。
领域事件的命名
- 如果套用英语的语法来说,一般是完成时 + 被动语态。比如说,订单已提交,这个“已”字就是完成时,代表已经发生的事情。
- 如果业务上已经有约定俗成的术语,我们就直接使用术语,这样更容易和业务沟通。例如:「已立项」是通用术语
识别「项目管理」流程的领域事件
- 第一行表示主流程,包括必然发生的事件
- 第二行表示可选的事件(包括异常事件)
所有流程的领域事件
领域事件需要注意两点
- 第一、不要把技术事件当成领域事件
- 领域事件一定要是领域专家所关注的,用的是业务术语
- 像数据库事务已回滚、缓存已命中之类的技术术语,不是领域事件
- 第二、查询功能不算领域事件
- 领域事件应该是对某样事物产生了影响,并被记录的事情
- 一般是某个事物的创建、修改和删除
- 还有一种情况是向其他人或者系统发消息,例如“通知邮件已发送”也算领域事件,因为接收方可能会通过进一步处理来影响某些事物
- 这并不代表查询功能不重要,查询功能也要以某种方式体现
领域事件小结
- 在这个过程里,我们已经开始形成统一语言。所谓统一语言,英文是 Ubiquitous Language,是 DDD 中的一个核心模式。指的是业务人员和开发人员使用的语言要一致。语言是知识的载体。语言一致就意味着背后对领域知识的理解一致。统一语言贯穿了 DDD 的全过程。
- 我们也开始识别业务规则
- 「协作」才是事件风暴的精髓,而具体结果怎样呈现,反而是第二位的
思考:当前识别的领域事件,主要是以「增加」的功能为主,你觉得需要把修改和删除的功能都列出来吗?
- 修改删除之类的,有相关的业务才需要列出来,比如合同签订了就只能违约,不能修改和删除,那就只有一个合同已违约,修改和删除甚至创建都一样是技术术语,事件风暴最好用业务术语
- 不需要列举修改和删除功能,避免冲淡了领域事件的聚焦。特别是对于修改和删除操作本身不复杂的领域事件,就更没有必要了;修改和删除所关联到的规则,可以在业务规则中说明
- 领域事件一定是领域专家所关注的,用的业务术语。如果修改会引起业务流程的变化,例如合同金额发生变化,那是需要单独列出来的。如果只是修改了某个备注字段,就不需要单独列出来
在做事件风暴的时候,如果事件本身很简单,但是业务规则非常复杂,这种情况怎么去揭示复杂的业务规则呢?
使用业务规则表
事件风暴第二步:识别命令
- 所谓命令(command),就是引发领域事件的操作,我们可以通过分析领域事件得到
- 除了识别出命令本身以外,我们通常还要识别出谁执行的命令,以及为了执行命令我们要查询出什么数据。
命令、执行者、查询数据简介
- 蓝 命令:对于「合同已签订」这个事件,对应的命令就是「签订合同」
- 粉 执行者:「签订合同」的执行者是「销售人员」
- 绿 查询数据:在执行「签订合同」操作时,「执行者」先从系统中查出客户信息,才能和中国客户签订合同。「客户」是一种「查询数据」
没有查询数据的命令
有多个查询数据的命令
有多个执行者的命令
事件风暴第三步:识别领域名词
- 这里说的领域名词,是从领域事件、命令、执行者、查询数据里找到的名词性概念
- 例如,对于签订合同这个命令而言,受到影响的名词性概念是「合同」
- 类似地,对于合同已签订这个领域事件,是由于「合同」这个名词性概念的状态变化所导致的。
识别领域名词的方法:从领域事件、命令、查询数据中寻找
第一步:把围绕同一个名词(相同/相关名词)的「领域事件」放在一起,按「名词」分组
第二步:将「领域事件」对应的「命令」、「查询数据」加入分组
第三步:将「领域名词」加入分组
所有领域名词
同一个概念在图中出现多次怎么办?
在「领域事件」、「命令」后面加数字(表示第几次出现)
补充说明
「事件风暴」中没有梳理出来的业务逻辑,可以留到「领域建模」时再梳理
再谈事件风暴的作用
- 事件风暴是业务人员和技术人员一起协作,捕获行为需求、消化领域知识、形成统一语言的一种方法
- 领域事件的作用
- 从代码实现的角度来看,领域事件一般会对应一段代码逻辑,这段逻辑可能会最终改变数据库中的数据
- 在事件驱动的架构中,一个领域事件可能会表现为一个向外部发送的异步消息。
- 命令的作用
- 「领域建模」时,我们可以通过对命令的走查(walkthrough),细化和验证「领域模型」
- 在实现层面,一个命令可能对应前端的一个操作,例如按下按钮;对于后端而言,一个命令可能对应一个 API
- 命令的执行者的作用:在「领域建模」时,执行者可能本身就是一个「领域对象」,也可能是领域对象充当的角色,或者是权限管理中的一个角色
- 查询数据的作用
- 查询功能不产生领域事件,因此也不会有相应的命令
- 每个查询数据,就对应着一个查询功能。不过,这里的查询数据是为了某个命令服务的
- 系统中可能还有一些单纯的查询功能,并不与某个特定的命令绑定。这些查询功能不会通过事件风暴识别出来,需要单独进行分析
- 领域名词的作用
- 识别领域名词的最终目的是要找到领域模型中的对象
- 不把这一步直接叫做识别领域对象的原因
- 在这一步里识别出的名词,虽然很可能就是「领域对象」,但也未必
- 一个名词有可能只是一个对象充当的角色,或者对象的属性,还有些名词需要经过合并或拆解后,才是合理的「领域对象」,而这些需要等到领域建模时才能真正搞清楚
- 事件风暴本身并不是进行这种深入分析的合适工具,所以,我们在这一步只需要识别出领域名词就可以了,这些名词将成为领域建模的「素材」。
事件风暴的常见问题
第一个问题:在事件风暴里是否要列出所有的领域事件和命令?
- 在事件风暴里只列出主要的、足以用于表达和交流领域知识的步骤(例如签订合同、生效合同等等。而像修改合同和删除合同这样的步骤是显而易见的,在讨论过程中可以提一下,但不必真的列出来,这样是为了保持简洁。)
- 列出所有「领域事件」和「命令」,会让结果变得繁琐,反而让人抓不住重点
- 不列出来,怎么保证这些功能不被遗漏呢?我们可以结合用户故事或者传统的功能列表等方法保存系统功能的全集
第二个问题:各个领域事件需要体现严格的时间顺序吗?
- 只需要按照大致的顺序,贴出领域事件就可以了
- 这是因为,如果要体现严格的时间顺序,需要用到更复杂的符号,例如条件判断,还有要画更多的连线,这会使事件风暴变得非常繁琐
- 应该关注点分离:如果要体现严格的时间顺序,我们可以用流程图、顺序图等方法,但事件风暴不必关注这一点
第三个问题:每个步骤的颗粒度应该有多大?
这里说的步骤,指的是一对「领域事件」和「命令」
比如说,「签订合同」这个命令,在具体操作的时候,可能分成录入合同基本信息、录入合同明细、上传附件等更小的步骤。那么,我们需要为每一个小步骤都识别出领域事件和命令吗?
从业务视角来看,是把每个小步骤都当作独立的一个事务来看待,还是把它们合起来作为同一个事务来看待?
如果每个小步骤都向外界发出一个领域事件,对系统后续的功能是不是有意义?
在目前的需求里,合同作为一个整体来提交就可以了,分成小的领域事件,并没有意义,所以不再分成更小的步骤了。
在实践里,遇到模棱两可的情况时,原则上宜粗不宜细。可以先采用比较大的颗粒度。后面必要的时候,再拆细,就可以了。
第四个问题:事件风暴适用于所有项目吗?
- 事件风暴主要应用在需求不清晰,或者理解不统一的情况下,通过协作的方式理清业务、达成一致,所以通常对于新项目比较适用
- 对于遗留系统改造的情况,如果这个系统的知识已经流失得很严重,那么事件风暴仍然是有意义的。但如果大家对这个系统的业务知识很清楚,只是要进行架构改造,那么事件风暴的意义就不大了
- 与事件风暴类似的方法:用例分析、用户故事
除了绘图之外,事件风暴也可以用表格的形式来保存
业务流程 | 命令 | 领域事件 | 执行者 | 查询数据 | 领域对象 |
---|---|---|---|---|---|
租户管理 | 添加租户 | 租户已添加 | 管理者 | - | - |
组织管理 | 添加开发中心 | 开发中心已添加 | 人事人员 | 企业 | - |
组织管理 | 添加开发组 | 开发组已添加 | 人事人员 | 开发中心 | - |
组织管理 | 添加直属部门 | 直属部门已添加 | 人事人员 | 企业 | - |
员工管理 | 添加员工 | 员工已添加 | 人事人员 | 所属部门 | - |
员工管理 | 转移员工 | 员工已转移部门 | 人事人员 | 目标部门 | - |
表中的「领域对象」需要在领域建模阶段才能分析清楚
使用领域规则表保存领域规则
规则编号 | 模块 | 规则描述 | 举例 | 影响的主要功能 |
---|---|---|---|---|
R001 | 项目管理 | 任一时刻,一个员工在各个项目上累计的预计投入工作量不能超过 100% | 小王在项目 A 上预计投入 60%,如果现在要把他同时分配到项目 B 上,那么项目 B 上的预计投入工作量不能超过 40% | 报工时 |
R002 | 工时管理 | 员工只能在被分配的项目上报工时 | 小王被分配到项目 A 上,但没有分配到项目 B,则小王周一可以在项目 A 上报 4 小时,但不能在项目 B 上报工时 | 报工时 |
R003 | 工时管理 | 一个员工一天累计工时不能超过 24 小时 | 小王被分配到项目 A 和 B 上,周一在 A 上报了 12 小时,在 B 上报了 13 小时,由于超过 24 小时所以不能通过 | 报工时 |
R004 | 工时管理 | 员工只有在项目有效期内才能报工时 | 小王被分配到项目 A,项目 A 在 8 月 1 日结束,小王想在 8 月 5 日为项目 A 报工时则不能通过 | 报工时 |
R005 | 工时管理 | 工时单位为小时,最小颗粒度为 0.5 小时 | 小王为项目 A 报 4.5 小时,可以通过,但报 4.6 小时,则不能通过 | 报工时 |
DDD 寻找「领域名词」的过程
- 通过头脑风暴,找出「领域事件」
- 由「领域事件」推导出「命令」
- 从「领域事件」、「命令」、「查询数据」里找到共同的名词,这些名词就是「领域名词」
名词解释
领域事件:业务中已经发生的重要事件,是业务方必须关心的,对业务流程有重大影响,发生后会改变系统数据。例如用户已注册、商品已下单。领域事件应该具备原子性,方便解耦、异步处理、追踪审计。查询功能不算领域事件。
命令:引发领域事件的操作,我们可以通过分析领域事件得到。除了识别出命令本身以外,我们通常还要识别出谁执行的命令,以及为了执行命令我们要查询出什么数据。例如用户注册、用户下单(需要查询出商品信息)。命令是对领域事件的描述,是对领域事件的触发。