作为TW技术咨询师,为多家企业进行架构和 Fintech 创新相关技术咨询,如架构设计、遗留系统上云迁移及规划、各种技术赋能、企业技术相关平台的生态规划及落地建设,创新实验室的技术部分筹建等等。此前,10多年的投行研发经验,包括外汇交易等核心系统,涉猎从架构、开发、Scrum Master、运维等多个角色。个人创业经验经历涉及互联网、金融和教育行业,并在广州多所重点大中院校担任创客导师。
又一年的DDD-China大会,碰上北京的初雪,这次终于没有因为太干燥而病倒了。
今年在大会的互动区给大家准备了一个事件风暴的小体验,参加了的小伙伴说“很爽”,那这里就把这个小案例分享一下,让更多人来一起爽 ^_^
故事背景
场景源于一个打劫案件,当劫匪拿出他的枪,来自未来的超能摄像头发现危险马上自动报警。同时,各位正义英雄暗地里开始出动!
哪知道这班劫匪也不是盖的,某正义英雄(请自动脑补蝙蝠侠)虽然第一个到场,和劫匪激战一轮居然也还没拿下,无奈中只能触发身上的升级预警装置通知总部增援。
总部系统收到通知马上召集了其他英雄线上会议商讨对策,作战计划一出,第二批英雄马上抵达现场,几经周折终于把劫匪抓获。
这里用一个简化版的用户旅程地图描述了这个场景。通常地,类似的用户旅程就正是掀起事件风暴的优秀开端~
现在让我们假设,作为英雄总部,我们计划基于这个场景构建一个自动预警系统,从而让整个预警过程更加智能更加顺畅,那我们IT民工该怎么和总部一起设计这个系统呢?
事件风暴-拉开序幕
当开启事件风暴,第一件事情是必须把自己的视角切换到自动预警系统的设计师这个角色上来,谨记,我们关注的是这个系统在这个场景下应该怎么运作,系统以外的细节我们可以暂且忽略。换而言之,“自动预警”就应该是核心域。
在正统的事件风暴过程中:
- 第一步就是寻找事件并以“XX已YY”(如“订单已提交”)完成时态描述这个事件
- 第二步就是寻找这个事件对应的命令,通常是一个动宾结构(如“提交订单”)
而在这里,对于不太复杂的系统我会倾向与合二为一,同时去列举出“角色/读模型/命令/事件”。这里举一个?说明一下“角色/读模型/命令/事件”分别是什么:
- 角色 简单来说就是主语,可以是人物,可以是类似于定时任务的规则,可以是外部系统
- 读模型 (Optional)角色是基于什么信息做之后的决定的,不一定每步都有
- 决策/命令 原译Command,但叫“业务决策”可能更好理解,反正就是描述做了什么决定
- 事件 描述之前的决定触发了什么事情,或者说决策导致某东西的状态变了为什么。
当然,如果在做的过程中发现这样连起来想不清楚,那就还原基本步,按照原来的事件->命令这样小步走就好了.
Step 1. 命令风暴
下面是我自己设计的命令风暴,结果跟大家在互动区设计的还是差不多的。(我这个贴法跟正宗的有点不一样,主要是为了糊墙的时候省地方)
这个并不是标准答案,而且也并不存在标准答案。因为我们是在设计,一百个人就有一百个哈姆雷特,按团队商量统一一个思路走就可以了。
再列几个在这过程中常见的疑问:
在我的风暴中,会议商量出来的叫“作战计划”,现场童鞋商量出来的是“抓捕计划”,这个就是传说中的统一语言的过程,只要得到一个团队一致认可的名字就可以了。
风暴中,有“预警通知”、“警报”、“升级预警通知”,有童鞋会问是不是可以统一叫“通知”,这个其实也是统一语言和对业务的理解问题,如果觉得三个场景都是发送一样的通知就可以了,那统一叫“预警通知”是个不错的选择,但如果希望每次发送的内容都不一样,那还是得取个不一样的名字方便后续辨认区分。
现场童鞋曾经出现一个场景写到“英雄->登出会议->会议已结束”。这里会考虑登出和结束之间是不是漏了一些步骤,因为按套路走的话应该是“登出会议->会议已登出“,”结束会议->会议已结束“。现在写的决策和事件并不对应,可见应该有遗漏。
过程中,比如开会怎么商量,作战计划怎么执行,这些其实不是这个预警系统关注的事情,所以不需要进一步展开.
Step 2. 识别业务对象
简单来说,就是要找出之前那个”决策/命令“打算要操作什么东西?或者说那个“事件”里面“XX已YY“的XX说的是什么。
如果第一步是严格按照“XX已YY”的写法进行,这里应该难度不是特别大。
当然,如果在第一步里面把一些查询或者打开页面这类的动作也放进命令风暴的话,有可能那些步骤会找不出一个业务对象,或者就成为“XX页面”。这也是为什么通常在命令风暴中不考虑一些查询动作的原因,打开页面的动作并没有实际改变什么东西的状态。
Step3. 分析业务对象生命周期
在通常的事件风暴介绍中,“分析生命周期”经常就是一句话带过,但这里我会建议大家显式地把生命周期画出来,这样对于后续分辨聚合根/实体/值对象会很有帮助。
有一些信息只出现了一次,所以画出来就成了一个点,明显的这些可以先归为值对象,因为他们只是某个值,而没有生命周期,不需要id之类的标识。
另一类是一条线段,可见他们是有生命周期的,期间是带有状态的,这些就是传说中的实体了。
那哪些是聚合根呢?
- 简单来看,就是看这些点和线的归属。比如上图中,“案件记录”的生命周期是最长的,也包含了其他所有的业务对象生命周期,那看起来它就应该是聚合根。
- 再看“警报升级会议”,看起来没有案件就没有它的存在,所以它应该是属于“案件记录”这个聚合根里面的一个实体,但如果我们扩展一下分析的场景,比如假设有另外一个场景是说在平常其实英雄也需要开例会,而且对应的也是一样的创建/登入/登出/结束,我们觉得它们可以统一处理,那这样“会议”就会是另外一个聚合
- 同理,“警报升级通知“等的三个值对象,如果只在案件周期内存在,那他们就是隶属于案件的值对象。但如果我们有其他场景是会比如配置这些通知的不同模版的, 那这些通知就有了自己的生命周期,可能自己会独立成为聚合根
所以,看生命周期还得全面的看,在集合了多个场景之后才会比较准确。当然,里面也会有一些业务常识在,比如“会议”如果一开始我们就选择了Zoom之类的外部系统实现,那很明显它就应该独立出去自己聚合了。
Step4.
这里第四步包括了两部分:聚合细化 & 上下文分析,这两部分其实是相辅相成的,并没有绝对的顺序,有时候还会根据其中一步的分析结果来回调整另一步,所以这里我把它们归到一起。
Step4a. 细化聚合根分析
这里我们先假设,会议有其他场景也用到,所以它属于另一个聚合。
那下面我们就可以把上面的业务对象按照聚合的方式细化分析他们之间的关系了。并可以补充上一些关键的属性
Step4b. 识别上下文和调用关系
同理也可以划分出上下文以及他们之间的调用关系。(这里的U我是指消费者/调用方,而D是生产者/被调用方)
留意,这里说的是上下文,也就是业务边界,它跟服务划分并没有绝对的关系。
要问如果对应到服务划分,其实我们还得多看几个维度综合考虑,比如:
- 组织架构(康威定律)- 比如上面预警系统如果都是一个团队管,那会议和案件不太复杂的时候放在一个服务也不是不可以,但如果分别两个团队管,那还是分开好,免得吵架。
- 业务边界(上下文)
- 变更频率 - 比如营销活动上下文里面有营销活动的规则,活动的元素基本固定,但活动规则经常变,这样我们可以考虑从活动上下文中再分出活动规则
- 高并发、低延时等的非功能性需求 - 比如某些高并发场景需要读写分离,那就会把一个上下文再拆开读和写两部分
- 技术层面需求 - 比如因某些特性需要用不同的语言编写
- 安全等其他层面要求
至此,从用户旅程的一个场景,到业务领域的划分就完成了 ^_^