设计文档编写规范
本文档定义 Pragmatic DDD 框架下需求 / 迭代设计文档的统一结构与写作约束,基于 订单发货与物流信息修正拆分设计 提炼。设计文档是「改代码之前必须产出的唯一输入」,面向人评审与AI 落地两类读者。前置阅读:聚合业务规则(OrderRule 范式) · 应用层落地模式。
1. 本质与定位
设计文档用于回答「这次要改什么、为什么这么改、按什么顺序改、改完怎么验证」。它在代码之前产出,是评审依据与实现输入。
- 解决什么:需求未经分析就动手 → 只修表象;实现细节散落在对话里 → 无法评审、无法交接、无法复现。
- 两类读者,两种刚性要求:
| 读者 | 需要什么 | 文档必须提供 |
|---|---|---|
| 人(评审者 / 接手者) | 快速判断方案对不对、边界清不清 | 结论先行、影响面、方案对比、风险清单 |
| AI(执行者) | 不猜测地改出可编译代码 | 确切文件路径、确切类名 / 常量名、可复制代码骨架、验收测试点 |
TIP
硬性流程约束:任何代码改动前必须先产出设计文档并确认。分析类请求(未要求输出文档)只输出分析,不改代码。
1.1 与相关文档的区别
| 文档类型 | 回答什么 | 稳定性 | 位置 |
|---|---|---|---|
| 最佳实践(本目录其它篇) | 框架能力怎么用 | 长期稳定,沉淀复用 | documentation/best-practices/ |
| 设计文档 | 这次改什么 | 一次性,随需求迭代 | docs/design/{模块}/ |
| 核心文档 | 框架底层机制是什么 | 随版本演进 | documentation/core/ |
设计文档不重复讲解框架机制,只给链接;它引用最佳实践,不替代最佳实践。
1.2 存放位置
| 范围 | 路径 |
|---|---|
pragmatic-ddd-core 相关提案与重构计划 | docs/design/core/ |
| 示例工程与其它模块 | docs/design/{模块}/(如 docs/design/examples/) |
命名:{主题}-{动作}-design.md,如 order-ship-and-logistics-correction-design.md。
2. 文档骨架(固定结构)
一份设计文档由 12 节组成,顺序固定。章节编号与标题名保持一致,便于跨文档定位与 AI 抽取。
| # | 章节 | 必选 | 回答的问题 |
|---|---|---|---|
| 1 | 背景与问题 | ✅ | 现在出了什么事 / 要做什么,影响面多大 |
| 2 | 根因分析 | ✅ | 为什么会这样,分几层,本质是什么 |
| 3 | 设计目标与原则 | ✅ | 要达成什么,依据什么原则 |
| 4 | 方案(含对比 / 状态矩阵) | ✅ | 有哪几种做法,选哪个,边界在哪 |
| 5 | 详细设计(分层) | ✅ | 每层改什么,代码长什么样 |
| 6 | 规则校验设计 | 视场景 | 不变量怎么表达,读当前态还是旧快照 |
| 7 | 并发与幂等 | 视场景 | 重复提交、并发提交怎么防 |
| 8 | 审计与留痕 | 视场景 | 变更怎么追溯 |
| 9 | 时序 | ✅ | 一次调用的完整执行路径 |
| 10 | 改动清单与落地顺序 | ✅ | 改哪些文件,按什么顺序改 |
| 11 | 测试清单 | ✅ | 怎么验证改对了 |
| 12 | 风险与待确认事项 | ✅ | 有什么坑,什么没定 |
5 / 6 / 7 / 8 节是否必选取决于场景;1–4、9–12 恒必选。不适用时保留标题并写「本次不适用,原因:……」,不要直接删节——删节会让 AI 误判为遗漏。
3. 各节写作约束
3.1 结论先行(文档最顶部)
正文之前用引用块给出一句话结论,包含「选哪个方案」与「为什么」。评审者只看这一句就能判断方向对不对。
> 结论先行:**「发货」与「修正物流信息」必须拆成两个操作**。
> 现状 `ship()` 一个方法同时承担「推进物流状态」与「录入物流信息」两个职责……同时标注文档性质:> 本文只做设计,未改动任何代码。
3.2 根因分析:必须三层递进
这是设计文档最容易被跳过、也最有价值的一节。只写表象原因会导致「改了但没改对」。
| 层级 | 问什么 | 示例(发货重复执行) |
|---|---|---|
| 直接原因 | 哪行代码没拦住 | 规则没校验 shipmentStatus 维度 |
| 深层原因 | 为什么拦不住 | execute 先跑领域逻辑后校验,当前态已被改过 |
| 本质原因 | 为什么会有这个设计 | 两个职责耦合在一个方法上,需求互相冲突 |
写作要求:
- 每层都贴真实代码并给文件行号,不写「某某方法有问题」这种无法定位的描述。
- 引用代码用
\``行号:文件相对路径` 格式,可点击跳转。 - 本质原因必须落到职责 / 边界 / 模型层面,而不是「忘了写判断」。
3.3 方案:给出允许矩阵而非大段描述
状态类需求用矩阵表表达「什么状态允许什么操作」,比文字严谨且不易遗漏组合:
| 旧状态 | 操作 A | 操作 B |
|---|---|---|
PENDING | ✅ 放行 | ❌ 拒绝(还没发生,谈不上修正) |
并显式写出边界澄清:哪些场景不属于本方案(如「发错货重发」属新业务动作,走售后,不是纠错)。明确「不做什么」和明确「做什么」同等重要。
3.4 详细设计:按层组织,代码骨架可直接用
按 领域层 → 应用层 → 接口层 → 基础设施层 四层展开,每层都要写,包括「零改动」的层:
| 层 | 写什么 |
|---|---|
| 领域层 | 聚合方法、值对象、领域事件、规则容器、注册表 |
| 应用层 | Input、Updater、WriteService 入口 |
| 接口层 | HTTP 方法 / 路径 / Request、DTO 是否需改 |
| 基础设施层 | 表结构、仓储、投影、Mapper——即使不动也要写「零改动,理由:……」 |
代码骨架约束(AI 落地质量的关键):
- 必须是可直接编译的真实代码,含
import所依赖的完整类名、真实构造签名。 - 类注释写明「干什么」,作者统一
wizard-lee,不贴大段示例代码。 - 涉及调用顺序的,在代码后补一段顺序约束说明并解释「为什么不能换」,例如:
previousLogisticsInfo必须在赋值前暂存:先赋值则拿不到 before,先collectEvent则拿不到 after。
3.5 规则校验设计:明确读当前态还是旧快照
凡「判断状态是否被推进过」的规则,必须明确判定依据,这是本框架最高频的坑:
| 判定场景 | 读谁 | 原因 |
|---|---|---|
| 字段值不变量(金额为正、客户非空) | 当前态 | 与执行顺序无关 |
| 状态前置条件(仅待发货可发货、仅待支付可支付) | 旧快照 old | execute 先跑领域逻辑,当前态已被改 |
必须写出激活条件(基于 hasOperation)与新旧读法的对应关系,见 聚合业务规则(OrderRule 范式)。
3.6 改动清单:AI 的落地依据
用表格给出确切文件路径 + 动作 + 说明,三列缺一不可:
| # | 文件 | 动作 | 说明 |
|---|---|---|---|
| 1 | domain/.../operation/OrderOperationRegistry.java | 改 | 新增 CORRECT_LOGISTICS |
| 2 | domain/.../event/OrderLogisticsCorrectedEvent.java | 新增 | 携带改前 / 改后 + 原因 |
再单独给落地顺序(通常 ≠ 章节顺序),保证每一步都可编译、可单测:
建议按
1→4→5→3→2→6→7→8→9→10推进:先立规则与操作码,再改聚合,最后接应用层与接口层。
3.7 测试清单:编号 + 用例 + 期望
| # | 用例 | 期望 |
|---|---|---|
| T2 | 已发货订单再次发货(核心) | 拒绝,ORDER_SHIP_STATUS_INVALID,DB 不变、不发布事件 |
- 期望里写确切的消息码常量名 / 状态枚举值,不写「报错」。
- 标注哪几条是验收核心(如「T2 / T7 是本次验收核心」)。
- 区分规则层单测与应用服务集成测。
3.8 风险与待确认事项
风险表要给触发条件与应对,而不只是列问题:
| # | 事项 | 说明 | 建议 |
|---|---|---|---|
| R1 | MyBatis 一级缓存可能让旧快照失效 | 加 @Transactional 后同一 SqlSession 命中缓存,old == current | 配置 localCacheScope=STATEMENT 并加测试守住 |
顺带发现的同类缺口一律记入风险表并标注「不在本次范围」,用于另开文档——不在本次扩大改动范围。
4. 完整模板
以下内容可直接复制为新文档的初始骨架:
# {需求主题}设计
> 结论先行:**{选哪个方案}**。{一句话理由}。
>
> 本文只做设计,未改动任何代码。
## 1. 背景与问题
### 1.1 现象
{当前表现,贴代码 + 行号}
### 1.2 影响面
| 影响 | 说明 |
| --- | --- |
## 2. 根因分析
### 2.1 直接原因:{一句话}
{代码 + 行号}
### 2.2 深层原因:{一句话}
{代码 + 行号}
### 2.3 本质原因:{一句话}
{职责 / 边界 / 模型层面的分析}
## 3. 设计目标与原则
| 目标 | 说明 |
| --- | --- |
## 4. 方案
### 4.1 {操作 / 概念}定义与边界
| 维度 | A | B |
| --- | --- | --- |
### 4.2 状态机与允许矩阵
| 旧状态 | 操作 A | 操作 B |
| --- | --- | --- |
### 4.3 事件与投影
| 事件 | 携带内容 | 订阅者 | 投影影响 |
| --- | --- | --- | --- |
## 5. 详细设计
### 5.1 领域层
### 5.2 应用层
### 5.3 接口层
### 5.4 基础设施层(零改动时写明理由)
## 6. 规则校验设计
### 6.1 旧快照机制的复用
### 6.2 规则清单(本次之后)
## 7. 并发与幂等
## 8. 审计与留痕
## 9. 时序
## 10. 改动清单与落地顺序
## 11. 测试清单
## 12. 风险与待确认事项
## 附:与既有文档的关系5. AI 落地校验清单
设计文档写完后(或 AI 读取他人文档前),逐项自检。任一项不满足,文档不合格:
| # | 检查项 | 不满足的后果 |
|---|---|---|
| 1 | 顶部有结论先行块,含「选哪个 + 为什么」 | 评审方向不明 |
| 2 | 根因分析写了三层(直接 / 深层 / 本质) | 只修表象,同类缺陷复发 |
| 3 | 所有代码引用带 文件相对路径 + 行号 | AI 无法定位,凭猜测改代码 |
| 4 | 方案含允许矩阵或等价的穷举表 | 遗漏状态组合 |
| 5 | 明确写了「不做什么 / 不属于本方案」 | 范围蔓延 |
| 6 | 四层详细设计齐全,零改动层写明理由 | 误判为遗漏而改错地方 |
| 7 | 代码骨架可直接编译(真实类名与构造签名) | 编译失败,AI 反复试错 |
| 8 | 状态前置类规则明确标注读旧快照 | 规则写对但拦不住 |
| 9 | 改动清单含确切文件路径 + 动作 + 说明 | AI 找不到文件 |
| 10 | 给出落地顺序,且每步可编译 | 中途编译失败堆积 |
| 11 | 测试清单含确切消息码 / 枚举值,标出验收核心 | 无法判断改对了没有 |
| 12 | 风险含触发条件与应对,同类缺口标注「不在本次范围」 | 踩坑且范围失控 |
6. 常见反模式
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| 只有方案没有根因 | 方案看着合理但可能没对症,同类缺陷反复出现 | 必须写三层根因,本质原因落到职责 / 边界 |
| 根因只写「忘了加判断」 | 无法沉淀,下次还忘 | 追问到「为什么这个设计允许它被忘」 |
| 代码引用不带路径行号 | 评审与 AI 都无法定位 | 统一 \``行号:相对路径` 格式 |
| 只在正文里描述改动,无改动清单表 | AI 只能靠猜文件,易漏改 / 改错 | 独立改动清单表 + 落地顺序 |
| 期望写成「报错 / 失败」 | 无法断言,测试形同虚设 | 写确切消息码常量名与状态枚举值 |
| 状态前置规则读当前态 | 规则永远通过,缺陷依旧 | 先跑领域逻辑后校验,前置判断必须读 old |
| 基础设施层直接省略 | 读者误判为遗漏,或真的漏掉建表 / 改投影 | 保留标题写「零改动,理由:……」 |
| 顺带发现的缺陷顺手一起改 | 范围蔓延,评审与回滚困难 | 记入风险表,标注「不在本次范围,另开」 |
| 文档里重复讲解框架机制 | 文档冗长,且与核心文档双份维护 | 只给最佳实践 / 核心文档的链接 |
下一步
- 聚合业务规则(OrderRule 范式):规则容器、激活条件与旧快照判定
- 应用层落地模式:
execute模板与「先领域逻辑后校验」的执行顺序 - 操作注册表设计:新增操作时操作码的声明方式
- 事件建模指南:新增领域事件的写法
- 核心:业务规则引擎:
EntityRule/RuleCheckResult底层契约