聚合业务语义文档模板
落地路径:
docs/design/changes/active/{需求}/{agg}/semantics.md;需求归档时随目录移到changes/archive/{需求}/{agg}/。 适用:单个聚合本次需求的业务语义(新建 / 迭代)。 四份文档中的第一份契约,需求验收后归档;当前实现在design-doc-facts。
1. 这份文档是什么
1.1 定位
聚合交付链条上的第一份契约:描述本次需求中这个聚合应该是什么,不解释为什么这样设计。
| 文档 | 性质 | 回答的问题 | 结束方式 |
|---|---|---|---|
| 本次需求语义(本模板) | 契约 | 本次需求里它应该是什么 | 验收后归档 |
| 聚合写设计 | 契约 | 本次怎么实现 | 实施后归档 |
| 投影读设计 | 契约 | 本次怎么查 | 实施后归档 |
| 聚合事实 | 快照 | 现在实际是什么 | 永不归档,随代码更新 |
1.2 基线引用与增量写法
迭代需求不重写全量语义。本文档只按章重列受影响的章节,其余章节引用基线:
| 项 | 写法 |
|---|---|
| 头部基线 | 基线:aggregates/{agg}/facts.md(核对日 YYYY-MM-DD) |
| 受影响章节 | 完整重列(含未变的条目,保证单章可读) |
| 未受影响章节 | 写一行「无变更,见基线 §N」 |
| 新增章节 | 标「本次新增」 |
| 废弃内容 | 标「本次废弃」并写原因 |
| 与事实的差异 | 就地写在对应条目上((当前:xxx)),不另设差异章节 |
每章首行必须标本次处理方式,便于评审快速定位改了什么。
差异标注规则
- 写在哪:本次要求与 ④ 事实不一致的那个属性、动作、规则、事件、状态上,就在那一行就地补。
- 句式:
本次要求 ……(当前:……);差异较大时可在该条下冒一行⚠说明。 - 不写:与事实一致的条目不加标注;未变更章节不扫一遍差异。
- 不在本章做的事:不展开“怎么修”(归 ② 设计),不重复 ④ 的完整现状(引用即可)。
新建聚合(无基线)时全体章节重列,头部写 基线:无(新建)。
1.3 唯一事实源边界
| 内容 | 唯一出处 |
|---|---|
| 本次需求的属性、动作、事件、规则、状态、查询场景 | 本文档 |
| 表结构、索引、持久化映射 | 写设计文档 |
| 投影、查询条件、读模型 | 读设计文档 |
| 当前实际实现态(字段 / 消息码 / 实际挂载) | 聚合事实文档 |
| 为什么这样拆分、放弃了什么 | 设计决策记录 |
1.4 不写什么
- 不写技术实现:表名、字段类型、Topic、序列化、仓储实现。
- 不写设计取舍:为什么用旧快照、为什么事件不带全量。
- 不写历史:文档永远描述当前状态(或当前规划),变更交给版本控制。
- 不写图怎么读:图只呈现,不配说明书。
2. 两种输入源
2.1 现状型:从代码梳理
事实全部来自代码,逐项核对后再落笔。
| 维度 | 核对对象 |
|---|---|
| 属性 | 聚合根与子实体的字段 |
| 动作 | 聚合的公开业务方法 |
| 事件 | 聚合内收集事件的位置 |
| 规则 | 规则容器中的注册项与激活条件 |
| 状态 | 枚举取值与状态机推进点 |
| 查询 | 现有读服务与查询入口 |
产出价值有两个:把隐性语义显性化;把「文档写了但代码没做到」的地方暴露出来。后者记入附录 C。
2.2 规划型:从 PRD 设计
事实来自需求,字段名与类名可以是设计初稿。此时文档是实现前的契约。
- PRD 里的用户故事不是动作,需要提炼:一个故事可能对应多个聚合动作,也可能一个聚合动作都没有。
- PRD 里的校验条款进第 5 章规则,一句话一条,编号先分配。
- 需求没说清的地方不猜,进附录 C 记为待确认。
- 事件载荷允许先写业务信息、后补字段,但字段必须在实现前补齐。
- 查询场景必须问出来,不能由设计者代劳编造。
2.3 差异对照
| 维度 | 现状型 | 规划型 |
|---|---|---|
| 属性来源 | 代码字段 | PRD 业务对象 |
| 动作来源 | 聚合公开方法 | PRD 用例、状态定义 |
| 事件来源 | 事件收集点 | 需求中的异步通知、下游依赖 |
| 规则来源 | 规则容器 | 校验条款、业务约束 |
| 状态来源 | 枚举 | 需求状态定义 |
| 查询来源 | 现有查询入口 | 使用方提出的查询诉求 |
| 附录 C 内容 | 语义与实现偏差 | 需求缺口、待确认问题 |
| 验收标准 | 与代码一致 | 与 PRD 一致且可实现 |
3. 固定目录
章节名与顺序固定,不得增删主章节。每章首行标本次处理方式:重列 / 无变更,见基线 §N / 本次新增 / 本次废弃。
1. 聚合职责与边界
2. 聚合结构与属性
2.1 类图
2.2 聚合根属性
2.3 子实体属性
3. 业务动作
3.1 动作清单
3.2 动作写入的字段
3.3 动作的重复执行语义
4. 领域事件
4.1 事件清单
4.2 无人消费的事件
5. 业务规则
5.1 全局不变量
5.2 动作专属规则
5.3 动作 × 规则矩阵
6. 状态与流转
6.1 状态维度
6.2 状态流转过程
6.3 状态流转图
6.4 动作 × 状态矩阵
7. 外部依赖与协作边界
7.1 我依赖谁
7.2 输入与输出
7.3 依赖失败的业务后果
8. 查询场景
附录 A 术语与关键口径
附录 B 关键场景走查
附录 C 与实现(或需求)的偏差4. 逐章规约
4.1 第 1 章 聚合职责与边界
目的:一句话判断这个聚合管什么,以及哪些事别找它。
内容:负责一段、不负责一段。
形式:默认两段话。当聚合与外部存在 3 项以上争议边界时,改为「在聚合内 / 在聚合外」两列表。
硬规则
- 「不负责」必须写,且每一项都能在第 7 章找到对应依赖方。
- 一句话里要能看出这个聚合维护的核心不变性(金额、明细、状态之类)。
反例:「负责订单全生命周期管理」——等于什么都没说。
4.2 第 2 章 聚合结构与属性
目的:一张图看清构成与关系,三张表查清每个字段的业务含义。
2.1 类图
只画关键字段:标识、状态、金额、外部凭据;其余字段交给属性表。
硬规则
- 类名英文 + 中文标签,字段
+字段名: 类型(中文)。 - 必须画关系和基数(包含 / 属于 / 引用),这是图存在的理由。
- 枚举保留取值,取值带中文。
- 图里不重复属性表的全部字段——否则图变成一张竖排的表。
反例:18 个字段全列,读者首屏只看到一个长方形。
2.2 / 2.3 属性表
| 列 | 填写规则 |
|---|---|
| 属性 | 业务名,不用字段名 |
| 字段名 | 反引号包裹,与代码或设计稿一致 |
| 业务含义 | 一句话,能回答「这个字段解决什么业务问题」 |
| 业务口径 | 取值范围、单位、算式 |
| 谁能改 | 只填动作名,不填「系统」「外部」 |
硬规则
- 子实体必须有标识行,否则「改第 N 项」「删第 N 项」无所指。
- 派生字段必须在口径列写出算式。
- 「可变性」和「业务来源」合并为一列,两者本质是同一个问题的两面。
反例:可变性:不可改 + 业务来源:系统生成 两列内容同义。
4.3 第 3 章 业务动作
目的:列出这个聚合能被要求做什么,以及做完改变了什么。
3.1 动作清单
| 列 | 填写规则 |
|---|---|
| 业务动作 | 中文(英文方法名) |
| 业务含义 | 一句话,站在业务角度 |
| 状态变化 | 写成 A → B;不改状态写「无」 |
| 触发事件 | 中文(事件类名);不产生写「无」 |
| 相关规则 | R 编号 |
3.2 动作写入的字段
| 业务动作 | 写入的字段 |
|---|---|
| 动作 | 反引号字段名清单 |
3.3 重复执行语义
| 列 | 填写规则 |
|---|---|
| 再执行一次会怎样 | 拒绝 / 覆盖 / 合并 / 幂等 |
| 业务解释 | 为什么这样处理 |
硬规则
- 3.1 只写状态,不写字段;3.2 只写字段,不写状态。两表边界不得混。
- 「触发事件」不允许留空,没有就写「无」。
- 3.3 每行都必须能回答「用户连点两次会怎样」。
反例:3.1 写「记下流水号、优惠、实付、时间,支付状态转已支付」,3.2 又写一遍这几个字段——同一事实两处维护。
4.4 第 4 章 领域事件
目的:说明这个聚合向外界宣告了哪些事实,谁在听。
4.1 事件清单
| 列 | 填写规则 |
|---|---|
| 事件 | 中文(事件类名) |
| 事实含义 | 一句话,过去式:已经发生了什么 |
| 由哪个动作产生 | 动作中文 + 英文 |
| 载荷字段 | 字段名清单,不是业务描述 |
| 消费方 | 业务语义,不写 Topic 与消费者组 |
4.2 无人消费的事件
一句话 + 事件名清单 + 一句后果。没有消费方的事实要能看出「因此哪件事没人做」。
硬规则
- 框架级数据同步事件也要列,并说明触发时机。
- 载荷只带标识时,明确写「仅
entityId」,不要让读者以为带快照。 - 载荷列禁止出现「订单信息」「客户资料」这类笼统词。
反例:载荷写「订单标识、客户、订单项、订单总额、收货地址」,实际只带 entityId 与 customerId。
4.5 第 5 章 业务规则
目的:明确什么情况下允许做、做完是否合格。
结构:全局不变量(每次写动作都校验)+ 动作专属规则(带消息码)+ 动作 × 规则矩阵。
硬规则
- R 编号全文稳定,分配后不重排、不复用。
- 每条规则必须有判定基准:变更前看旧快照,变更后看结果。
- 依赖被动作改掉的状态时,必须按旧快照判定,否则规则会恒失败——这是本节最容易犯的错。
- 「守的是什么」不单列成列,与规则同义就删掉。
- 矩阵不重复全局规则,在表头说明即可。
反例:规则写「只有进行中才能取消」,却读变更后的状态,此时状态已改为已取消。
4.6 第 6 章 状态与流转
目的:用最少的图说清一个聚合随时间怎么变。
6.1 状态维度
一维一行:取值(含含义)、终态、由谁改变。不要把每个取值拆成一行、再用列重复维度名。
6.2 状态流转过程
起点、动作、业务条件、终点。终点必须写全所有被改变的维度。
6.3 状态流转图
每个维度一张图,不得合并成一张。合并后「互不推导」就看不出来了。
6.4 动作 × 状态矩阵
允许写「允许」,不允许写 ✗,每格必填。条件不在此复述,指向 6.2。
硬规则
- 多个状态维度必须显式声明「互不推导」,并给出证据句(哪个动作只推进一维)。
- 预留态必须标注「预留」,并说明为何可达 / 不可达。
- 不允许存在「既无入边也无出边、又未标预留」的取值。
4.7 第 7 章 外部依赖与协作边界
目的:说清这个聚合靠谁才能完成业务,以及它失败时业务算什么状态。
| 小节 | 内容 |
|---|---|
| 7.1 我依赖谁 | 依赖方、需要什么、哪个动作需要 |
| 7.2 输入与输出 | 一张图:左侧依赖,中间聚合,右侧事件 |
| 7.3 依赖失败的业务后果 | 失败情形、当前业务现状 |
硬规则
- 依赖方不超过 5 个。超过说明聚合边界或职责划分有问题,先回建模。
- 7.1 不写失败后果,统一放 7.3,避免两处维护。
- 7.2 只画契约(谁给我什么、我宣告什么)。协作的调用顺序属于应用层,画进来会腐化。
4.8 第 8 章 查询场景
目的:列出这个聚合需要被怎么查,作为投影读设计文档的唯一上游。
内容:一张表。
| 列 | 填写规则 |
|---|---|
| # | Q1…Qn,全局唯一 |
| 谁要查 | 业务角色:客户 / 客服 / 运营 / 对账程序 |
| 查什么 | 一句话,业务语言 |
| 用来做什么 | 业务目的:详情页、售后核对、导出、对账 |
| 业务筛选维度 | 业务词汇(订单状态、下单时间),不写字段名 |
硬规则
- 只写业务维度,不写查询形态(ById / 分页 / 滚动),形态归投影设计文档。
- 每条必须有「谁要查」与「用来做什么」,否则无法判断该不该支持。
- 不写排序、分页大小、返回字段。
- Q 编号全局唯一,投影读设计文档逐条对应,不新增编号。
- 查询场景与 4.2 的消费方可以重叠:订阅者存在的理由往往是让某个查询能查到。
反例:写「支持按 status + created_at 分页查询,pageSize 最大 200」。
5. 表达形式规约
5.1 三条总则
- 图只表达两种东西:结构与流转。清单、字段、规则一律用表。
- 被引用的东西必须有编号:规则必须 R 编号;动作与事件在需要跨章引用时才编号,一旦编号全文一致。
- 同一事实只定义一次,其他章节引用编号,不复制描述。
5.2 图的选型
| 要表达的内容 | 图类型 |
|---|---|
| 聚合构成与关系 | classDiagram |
| 单个维度的状态流转 | stateDiagram-v2 |
| 动作与事件、依赖与输入的对应 | flowchart |
| 调用顺序本身是业务语义时 | sequenceDiagram |
写法硬规则
- 节点文本含空格、斜杠、冒号、括号时,必须用双引号包裹。
- 类名使用英文标识 + 中文标签,泛型用
~T~。 - 图下方不写「这张图说明……」。
5.3 表的写法
- 一列只回答一个问题,禁止两列同义。
- 单元格内容超过一行为说明性长句时,删掉或移到附录,不要塞进表。
- 字段名、消息码一律反引号。
- 不允许出现空格子,没有内容写「无」或「—」,并保证全文用法一致。
5.4 命名
| 对象 | 写法 | 示例 |
|---|---|---|
| 动作 | 中文(英文方法名) | 支付(pay) |
| 事件 | 中文(事件类名) | 订单已支付(OrderPaidEvent) |
| 规则 | R + 序号,配消息码 | R11 / ORDER_PAY_STATUS_INVALID |
| 属性 | 中文属性名 + 反引号字段名 | 订单总额 / totalAmount |
| 状态 | 中文状态名,类图中标枚举值 | 进行中 / IN_PROGRESS |
| 查询场景 | Q + 序号 | Q3 |
6. 质量自检
写完逐条核对,任一不通过则文档未完成。
- 每个属性在「谁能改」里至少有一个动作。
- 每个动作要么产生事件,要么在 3.1 明确写「无」。
- 每条规则至少被一个动作触发;没有触发者的规则要么删,要么标注「防御性」。
- 每个状态取值要么有动作可达,要么标「预留」。
- 每个事件要么有消费方,要么进 4.2。
- 每个依赖方在 7.3 都有失败后果。
- 金额、数量类字段的等式有规则守护;没有守护的,进附录 C。
- 6.4 矩阵没有空格。
- 判定基准为「变更前」的规则,其依赖字段确实在变更后可读不到。
- R 编号无跳号、无重复、无重排。
- 图不重复属性表的全量字段。
- 附录 A 覆盖正文出现的所有业务名词。
- 查询场景每条都有「谁要查」与「用来做什么」,没有只写条件的条目。
- 查询场景只写业务维度,未出现查询形态、排序与分页参数。
- 头部标了基线(事实文档 + 核对日)或「无(新建)」。
- 每章标了本次处理方式,未变更章节有基线指向。
7. 新建一份文档的步骤
- 定输入源:现状型还是规划型,写在文档首部的信息块里。
- 列属性与字段,先落 2.2 / 2.3,因为后面所有章节都要引用字段名。
- 列动作,只写业务动作(不是用户故事,也不是应用服务方法)。
- 每个动作问三件事:改了哪些状态、写了哪些字段、宣告了什么。
- 把约束拆成规则,分配 R 编号,逐条标判定基准。
- 画结构图、状态图、输入输出图(不画流程图)。
- 列依赖与失败后果。
- 列查询场景:谁要查什么、目的是什么、按哪些业务维度筛。
- 写附录,最后跑第 6 节自检。
附录 A 空骨架
# {聚合名}聚合 · 业务语义描述
> 聚合:{Aggregate}({中文名})
> 依据:{代码现状 / PRD 名称与版本}
> 类型:现状型 / 规划型
## 1. 聚合职责与边界
**负责**:……
**不负责**:……
## 2. 聚合结构与属性
### 2.1 类图
(classDiagram:关键字段 + 关系 + 基数 + 枚举取值)
### 2.2 聚合根属性
| 属性 | 字段名 | 业务含义 | 业务口径 | 谁能改 |
### 2.3 子实体属性
| 属性 | 字段名 | 业务含义 | 业务口径 | 谁能改 |
## 3. 业务动作
### 3.1 动作清单
| 业务动作 | 业务含义 | 状态变化 | 触发事件 | 相关规则 |
### 3.2 动作写入的字段
| 业务动作 | 写入的字段 |
### 3.3 动作的重复执行语义
| 业务动作 | 再执行一次会怎样 | 业务解释 |
## 4. 领域事件
### 4.1 事件清单
| 事件 | 事实含义 | 由哪个动作产生 | 载荷字段 | 消费方 |
### 4.2 无人消费的事件
……
## 5. 业务规则
判定基准:变更前看旧快照,变更后看本次结果。
### 5.1 全局不变量
| 编号 | 业务规则 | 消息码 |
### 5.2 动作专属规则
| 编号 | 业务规则 | 消息码 | 触发动作 | 判定基准 |
### 5.3 动作 × 规则矩阵
| 业务动作 | 命中的专属规则 |
## 6. 状态与流转
### 6.1 状态维度
| 维度 | 取值 | 终态 | 由谁改变 |
### 6.2 状态流转过程
| 起点 | 业务动作 | 业务条件 | 终点 |
### 6.3 状态流转图
(每个维度一张 stateDiagram-v2)
### 6.4 动作 × 状态矩阵
| 业务动作 | 状态一 | 状态二 | 状态三 |
## 7. 外部依赖与协作边界
### 7.1 我依赖谁
| 依赖方 | 需要它给什么 | 哪个动作需要 |
### 7.2 输入与输出
(flowchart:左侧依赖,中间聚合,右侧事件)
### 7.3 依赖失败的业务后果
| 失败情形 | 业务现状 |
## 8. 查询场景
| # | 谁要查 | 查什么 | 用来做什么 | 业务筛选维度 |
## 附录 A 术语与关键口径
| 术语 | 口径 |
## 附录 B 关键场景走查
| 步 | 动作 | 维度一 | 维度二 | 维度三 | 产生事件 |
## 附录 C 与实现(或需求)的偏差
| 偏差 | 位置 | 严重度 | 影响 |