工程手记

两天,27 个模块的设计文档

批量产出设计文档的方法——统一模板、共享核心模型、按模板填充。27 个业务模块若逐个手写撑不住进度,通过先定死文档骨架和跨模块通用数据结构,可以降低重复劳动。

去年 4 月底,地陪服务平台脚手架期启动一周后,需要在两天内产出 27 个业务模块的设计文档。10 项细分服务、多角色分账模型(平台/商家/代理/分销/地陪)、完整后台,每个模块一个独立设计文档——如果逐个手写,进度撑不住。

这篇记录批量产出这类文档的方法论。核心思路是先定统一的文档模板与字段规范,再定跨模块共享的核心模型,最后按模板填充各模块。要点在于:共享模型必须先定死,因为改一次就要改所有相关文档。

先定文档模板骨架

27 个模块的设计文档虽然内容不同,但结构可以统一。我设定的骨架如下:

  • 1. Goal:这个模块解决什么
  • 2. Recommended Approach:为什么采用这个方案而不是其他(这很关键,很多团队会漏掉)
  • 3. Scope:包含什么、不包含什么(防止后续蠕虫式功能膨胀)
  • 4. Information Architecture:如果涉及后台,菜单信息架构怎么组织
  • 5. Page Design:UI 分区、筛选项、表格列、统计卡片等
  • 6. Data Model:数据库表结构、字段、枚举、索引
  • 7. API Design:接口地址、参数、响应格式
  • 8. Testing Strategy:前后端各自怎么测

这个骨架对所有 27 个模块都适用。没有骨架时,每个模块文档的结构都不一样,审查和理解成本翻倍;有骨架后,新增模块只需要按位置填空。

定死五个跨模块共享的核心模型

更关键的是定死核心数据模型,特别是账本和订单。一旦这些模型后改,27 个文档都要返工。

1. 订单模型

订单需要覆盖所有场景:用户下单、服务提供者接单、支付、取消、改期、超时计费、位置追踪、评价。这 10 项服务都公用一套订单模型,字段只增不减。

核心字段:orderIduserIdescortId(服务提供者)、serviceIdstatus(状态机)、amountcreatedAtstartTimeendTimecancelledAt 等。

最容易踩的坑:不要为了"简洁"就给每种服务拆一个订单表。一开始拆,后面统计、对账、消息推送都是灾难。

2. 账本模型(最复杂)

这是文章的重点。27 个模块里,账本类模块有 5 个:

  1. Platform Ledger(平台收支)
  2. Merchant Ledger(商家明细)
  3. Agency Ledger(代理商明细)
  4. Distributor Ledger(分销商明细)
  5. Escort Ledger(地陪/达人明细)

再加上 3 个交易流水类:

  1. Recharge Records(充值)
  2. Refund Records(退款)
  3. Withdrawal Accounts(提现账户)

这 8 个都遵循同一套流水结构,共通字段有:

{
  ledgerRecordId,      // 唯一标识
  role,                // 账本角色 (user/escort/merchant/distributor)
  direction,           // 方向 (income/expense)
  amount,              // 金额
  remark,              // 备注
  orderNo,             // 源单号(可追溯)
  recordedAt,          // 记账时间
  createdAt,
  updatedAt
}

角色特定字段会有差异(比如 Merchant Ledger 多了 paymentMethodbalanceAftersceneType 等),但这套基础字段集合对所有 8 个账本都适用。

最关键的决策:每个角色独立一张表,不要合并。 很多人会想"都是账本,能不能用一个大表加 role 字段区分?"不行。原因有三:

  • 查询效率:同一个角色的流水集中在一张表,索引策略清晰;混在一张表里,where 条件变复杂
  • 扩展性:未来某个角色的流水逻辑变复杂,新加字段只影响这张表
  • 权限隔离:后台按角色展示不同的账本页面,数据表和权限边界对齐,防止权限越界的 Bug

3. 单笔订单如何拆成多条流水(多角色分账)

这是设计中最容易出错的地方。用户下单一笔,金额是 100 元。这 100 元要在 5 个账本里都体现:

  • Platform Ledger:+100(收入)
  • Merchant Ledger:-70(服务提供者从平台结账)
  • Escort Ledger:+60(地陪佣金)
  • Agency Ledger:+5(所属代理分成)
  • Distributor Ledger:+5(所属分销分成)

一笔订单,拆成 5 条流水,分别计入 5 个账本。这不能在前端聚合,必须在订单产生时就由 order-service 通过 MQ 广播给各个 ledger 消费者异步处理。

4. 幂等键设计

MQ 消息可能被重复消费。幂等键设计决定了"重复消费时是否产生重复的流水记录"。

常见的幂等键结构:{orderId}-{ledgerRecordType}-{direction}-{roleId}

比如:"order-12345-commission-income-escort-789"

这个 key 存到 Redis,TTL 设为 24 小时。消费者拿到消息后,先检查这个 key 是否存在。如果存在,说明之前处理过,直接返回;如果不存在,才生成一条新的 ledger_record。

不这样做的后果:用户投诉"我下单了,怎么平台账户进账两次",查日志发现订单服务发了两条相同的 MQ 消息,或者消费者重启期间消息重新投递了。

5. 对账口径

"对账"是财务部门关心的。账本数据必须能对应到源单据。每条流水都要有 orderNo 字段(或其他源单号),这样财务人员可以随时拿着订单号查到对应的账本记录。

后台的"平台收支"页面,最关键的操作是能按订单号检索流水。如果没有这个,财务无法核账。

按模板批量填充

定好模板和核心模型后,填充就快了。以 Merchant Ledger 为例,从模板复制到文档,然后:

  1. Goal 部分:改成"查看商家账户余额流水"
  2. Recommended Approach:解释为什么独立一张表而不是从商家余额字段反推
  3. Scope:列出这个模块包括查询、删除,不包括批量删除、导出
  4. Information Architecture:菜单路由 /finance/merchant-ledger
  5. Page Design:筛选项(商家、支付方式、时间)、表格列(商户名、手机号、金额、余额、时间等)
  6. Data Model:表名 merchant_ledger_records,字段集合
  7. API DesignGET /admin/finance/merchant-ledgerDELETE /admin/finance/merchant-ledger/:id
  8. Testing Strategy:参数转发、筛选正确、金额计算正确

从模板到完成,一个模块 20-30 分钟。27 个模块,按这个节奏,两天内完全可以产出。

避免的陷阱

陷阱 1:过度个性化。如果每个模块都要求"独特的设计",模板就没用了。要求统一:所有后台列表页遵循同一个 UI 结构,只改列和筛选项。

陷阱 2:字段命名不一致。A 账本叫 ledgerRecordId,B 账本叫 recordId,C 账本叫 id。查询时容易出错。从一开始就定好规范,全部遵循。

陷阱 3:索引规划滞后。数据模型写完了,才想起来没有索引。到了后期"查询慢"才加索引,可能已经影响了代码逻辑。索引要在设计文档里就列出来。

陷阱 4:忽略 Not Included。设计文档里不写"不包含什么",后续就有人问"为什么没有批量删除""为什么没有导出"。明确列出 Scope 的边界,能省很多周期。

总结

批量产出设计文档的要诀是复用而非重复编写。文档模板保证了结构统一,核心数据模型保证了逻辑一致,两者结合,27 个模块的设计工作量从"无法估算"变成"可预测"。

最后的提醒:这套方法对脚手架期这样"框架先行、业务后补"的场景特别有效。但如果业务需求本身就不清楚,文档多了也没用——反而会变成"写了一堆高保真但实际没用的文档"。务必确保需求先冻结,再启动文档产出。

星野的头像

星野 XINGYE

一个人维护 AI 平台的工程师。这里记录 63 篇复盘:18 份故障档案、OTA、架构演进与工作流。