通用语言与事件风暴落地
战略设计 · 第 7/15 篇
上一篇:《战略建模三板斧:四色建模、用例分析、领域故事》 · 下一篇:《分层架构与四种模型:失血、贫血、充血、胀血》
开头:产品和开发说同一种话,有多难?
项目初期应思考如何用业务语言描述和构建系统,而不是先想「表怎么建、接口怎么写」。技术是服务业务的;脱离业务谈架构往往是空谈。DDD 战略阶段的抓手,一是通用语言(Ubiquitous Language),二是把动态流程建模式的 Event Storming(事件风暴)——弥补 DDD 书本「有分类框架、缺建模过程」的空白。
本篇聚焦 UL 的提炼原则与事件风暴的准备、事件/命令分析、聚合建模及 CQE 衔接;面试软文、简历类内容从略。
一、通用语言(Ubiquitous Language)
1.1 它解决什么问题?
传统迭代里常见两道沟:
- 设计与编码断层
- 业务与开发隔离
通用语言要求:产品、领域专家、开发、测试在同一领域内用同一套术语交流,从讨论到代码实现名词一致,减少歧义与返工。
1.2 定义与获取
定义:领域知识的提炼物——① 统一领域术语;② 领域行为描述。
获取:本质是需求分析与对齐的过程;讨论与代码实现使用相同词汇;术语既要有内涵也要有外延(边界清楚)。
关键约束:说某个词时,必须明确属于哪个限界上下文——同一词在不同上下文含义可不同,但上下文内必须唯一。
1.3 与 DSL 的关系
领域特定语言(DSL)专注某应用领域的计算机语言。通用语言偏业务分析与建模;DSL 偏将语言模块化、自动化落地(如代码生成)。二者都强调领域名词,层次不同。
1.4 实践要求
好的通用语言应:
- 表意明确:少解释即懂业务语义
- 认知统一:全员同一标准
- 简单易学:学习成本可控
电商等领域可整理术语表(订单、SKU、履约、退款等)与四色建模词汇对照,作为团队词典。


二、事件风暴(Event Storming)是什么?
由 Alberto Brandolini 提出,结合 Gamestorming 与 DDD,在几小时到几天内让团队形成对领域的共同理解。不限于软件——复杂业务都适用。
核心过程:
- 头脑风暴列出所有领域事件(过去式动词:订单已创建、支付已完成)
- 为事件标注命令(Command)与触发角色
- 分类、聚合,得出实体、聚合根、领域服务与限界上下文
相对「静态类图 / 表结构分析」,事件风暴是由静到动:从业务流程与事实链入手建模。
三、工作坊准备
3.1 物料与场地
- 多色贴纸、水笔、胶带/磁扣
- 足够大的墙、可站立讨论的会议室(尽量无椅子,减少分心)
- 全程放下手机与电脑,专注业务
3.2 卡片类型(常用)
| 类型 | 说明 | 示例 |
|---|---|---|
| 领域事件 | 已发生的事实,过去式 | 商品已加入购物车 |
| 命令 | 触发事件的意图 | 加入购物车 |
| 角色 | 发起命令的主体 | 买家 |
| 外部系统 | 域外触发源 | 支付渠道 |
| 策略/规则 | 事件发生的条件 | 购物车已满则不可加 |
| 定时器 | 时间触发 | 下单 1 小时未支付则取消 |
| 读模型 | 支撑决策的查询视图 | 购物车列表 |
| 聚合 | 一致性边界 | 订单聚合 |
| 遗留问题 | 暂存争议点 | 菱形卡片 |

3.3 参与角色
用户代表、产品、项目、开发、测试、架构师、领域专家(懂行业标准、未必是技术人员),以及引导者(控节奏、收敛讨论)。
四、领域事件分析
4.1 什么是领域事件?
- 领域中业务上真实发生、对后续有影响的事实
- 常由「当 X 发生,则 Y」「做完 A 请通知 B」等句式识别
- 可切断模型间强依赖,支持最终一致性;跨微服务时尤甚
识别原则:
- 重结果、轻手段:「笔试结果已通知」优于「笔试结果短信已发送」
- 重状态结果、轻中间规则记录:「简历不可重复投递」的本质不是「去重记录已创建」
- 避免伪事件:「XX 已查看」通常无业务后果;「XX 已修改」过宽,应拆细
4.2 分析步骤
第一步:罗列事件
- 过去式命名:
UserCreated、OrderPaid - 暂不确定的贴遗留问题卡,不删除
第二步:整理时间线
- 同类事件同一行;时间向右展开
- 合并重复、剔除非事件;全员对命名达成共识
电商 C 端示例事件链:浏览 → 加购 → 下单 → 支付 → 发货 → 收货 → 退款申请……

4.3 事件在架构中的使用
| 场景 | 要点 |
|---|---|
| 微服务内 | 聚合间事件;同进程可不用 MQ,但多聚合同事务需考虑事件总线与「一事务一聚合」 |
| 微服务间 | 解耦、最终一致;需事件构建、持久化、消息中间件、幂等等 |
五、命令风暴(Command Storming)
在事件旁补充谁用什么命令触发了什么:
- 贴角色、命令、外部系统、定时器、策略
- 用箭头连接事件 → 事件的因果关系
- 实线:强一致(须同时发生);虚线:弱一致/最终一致
讨论时从用户视角判断:两件事是否必须严格同时?高并发下滥用强一致会徒增分布式事务成本。
六、领域建模三步收敛
6.1 提取领域对象
从事件/命令的名词提取实体与值对象:
- 「订单已创建」→ 订单
- 「库存已锁定」→ 库存
实体:有唯一标识,生命周期贯穿业务;值对象:无标识,描述属性组合(如地址)。
6.2 构建聚合
- 选聚合根(唯一修改入口)
- 聚合内遵守不变式(如订单总额 = 明细之和)
- 小聚合、跨聚合用 ID 引用、跨聚合变更用领域事件最终一致
- 一次事务只改一个聚合
设计聚合的五条原则(简述):边界内不变式、小聚合、ID 引用他聚合、边界外最终一致、跨聚合编排放应用层。

6.3 映射限界上下文与微服务
多个聚合划入同一业务上下文 → 确定 BC 边界 → 再映射服务(一 BC 常对应一服务,高性能场景可一聚合一服务)。
七、CQE 与内部视图
Command / Query / Event 语义分离(来自 CQRS 思想):
| 类型 | 含义 |
|---|---|
| Command | 改变状态,应有明确接受结果 |
| Query | 只读,无副作用 |
| Event | 已发生事实,驱动后续写操作 |
价值:降低读写耦合、缓解高并发读写压力、避免聚合间不当耦合。
领域命令引发状态变更后,常发布领域事件;典型流程:
- 资格校验(Qualification:远程/本地)
- UpdateState 事务内改状态
- Publish Domain Event 供订阅方处理

内部建模可用活动图(分支)、时序图(交互)、状态机(状态流转)。订单与物流状态示例:
| 领域命令 | 订单状态 | 物流状态 |
|---|---|---|
| createOrder | 已创建 | — |
| payOrder | 已支付 | 待发货 |
| deliverGoods | — | 已发货 |
| receiveGoods | — | 已收货 |
| closeOrder | 已关闭 | — |
八、领域服务 vs 应用服务
领域服务:动词性逻辑不适合放进单一实体,或需协调多实体且无自然「主体」时——无状态、表达领域概念,如认证规则封装。
应用服务:编排领域对象、控制事务与权限;跨聚合流程放应用层,同一聚合内多实体协作放领域服务。
避免过度领域服务导致又回到「一切在 Service」的贫血局面。
九、基础设施建模
基础设施层承载持久化、消息、第三方适配等技术细节,与领域核心分离——战术篇会结合 Repository、六边形等展开。
本篇小结
| 阶段 | 产出 |
|---|---|
| 通用语言 | 上下文内术语表、行为描述 |
| 事件风暴 | 事件时间线、命令与角色、聚合与 BC |
| CQE | 读写与事实分离,衔接实现 |
事件风暴不是贴贴纸游戏:领域专家深度参与、命名共识、正确区分事件与命令、谨慎选择一致性级别,才能从 wall of stickers 走到可实现的模型。掌握 UL + Event Storming,战略设计才算从「概念」落到「可协作的方法」。