Skip to content

架构决策记录 (ADR) ​

概述 ​

ADR(Architecture Decision Record)记录 Walnut Admin monorepo 项目中的关键架构决策。

当前共 27 条 ADR,涵盖包命名、TypeScript 配置、依赖治理、发布流水线、测试策略、验证策略、包重组、Git 钩子、数据库选型、后端 lib 分层、契约门禁、健康探针、定时任务、签名与数据权限等领域。

形态约定(2026-09-23 起,由 pnpm lint:adr 机械强制) ​

在此之前,19 篇 ADR 的 **Status:** 有三种互不兼容的取值(Accepted ×12 / Implemented ×4 / In Progress ×1),「备选方案」则有四种记法(独立小节没有、Context 里的粗体表、Decision 里逐个子决策 各一段、散在正文的「为什么不…」段落)—— 等于没有约定。现在统一成下面这个形状:

md
# ADR-NNNN: <标题>

**Date:** YYYY-MM-DD
**Status:** Accepted

## Context          ← 必需
## Decision …       ← 必需,至少一个(多决策写成 `## Decision 1: …`)
## Alternatives considered   ← 必需,恰好一个,排在第一个 `## Decision` 之后
## Consequences     ← 必需
## Related          ← 可选

**Status:** 只有四种取值 ​

取值含义
Proposed提出来了,还没定
Accepted决策在生效(不是「代码写完了」—— 实现进度属于正文)
Rejected讨论过、明确否决了。否决也要留痕,这是本枚举存在的首要理由
Superseded by ADR-NNNN被后来的某篇取代;NNNN 必须真实存在

⚠️ Implemented / In Progress 不再合法 —— 它们把「决策算不算数」和「代码写完没有」混为一谈。 真需要表达「部分落地」,写进 ## Implementation Progress 或 ## Consequences(ADR 0017 就是这么做的)。

## Alternatives considered 写什么 ​

只写本篇文档真正记过的备选方案。 允许两种来源:

  1. 篇内已有的取舍论述(**Alternatives considered:** 块、表格里 Rejected 那一列、正文里的 「为什么不…」段落)—— 把它们搬进这一节,别留两个家;
  2. 把决策取反得到的那个显然备选(「不引入 X」的备选就是「引入 X」),但理由只能来自本篇; 本篇没给理由就如实写「本文档未展开理由」。

不许编造历史:不引入本篇没引用过的来源,不引用更晚的 ADR,不写 **Date:** 之后才发生的事。 门禁只查形态(有这一节、有列表项),内容质量是评审的活 —— 硬拦只会逼出凑格式的空话。

加一篇新 ADR 时 ​

  1. 文件名 NNNN-<kebab-slug>.md,编号从 0001 起连续(不许跳号、不许重号);
  2. 照上面的形状写全五个部分;
  3. 在下面的表格里加一行,状态列写 **Status:** 的原文(门禁要求两处逐字一致)。

漏掉第 3 步、写错编号、状态不在枚举内、少了任何一个小节 —— pnpm lint:adr(在 prepush 推送前门禁表里、ci.yml 的 quality job、以及发版电池里)都会直接红。

ADR 列表 ​

ADR决策状态
0001包命名——诚实命名,不用 "shared" / "core"Accepted
0002双模式消费——"source" 给 Vite,CJS 构建给后端Accepted
0003共享包不做环境变量默认值Accepted
0004直接 import @walnut/contract,不加包装层Accepted
0005前端 only 包 JIT(源码),共享包构建 CJSAccepted
0006按运行时 API 依赖分层Accepted
0007后端 libs 保留为 NestJS CLI 内部 monorepoAccepted
0008统一版本号,独立部署Accepted
0009三级质量门禁:commit → push → CIAccepted
0010不用 TypeScript Project ReferencesAccepted
0011catalogMode: strict、pnpm 原生发版(单一 fixed 组)+ git-cliffAccepted
0012前后端工具链分歧(tsconfig 独立、env 加载、hoisting、边界)Accepted
0013Barrel export 策略——桶由「有没有真实消费者」决定(2026-11 修订),不用 export *Accepted
0014ESLint 配置策略——共享 config 包 + 三预设Accepted
0015测试策略——Vitest 统一、co-located、分层覆盖率Accepted
0016验证策略——保持 class-validator,暂不迁移 ZodAccepted
0017Package 重组——多维标签 + 目录分组 + 新增包规划Accepted
0018Git Hooks——lefthook 取代 simple-git-hooksAccepted
0019共享 tsconfig 预设包与「无 .mjs」约束Accepted
0020移除 unplugin-auto-import,隐式全局改显式 importAccepted
0021数据库从 MongoDB 整体迁移到 PostgreSQLAccepted
0022后端领域 lib 分层与组合根形态Accepted
0023契约上收与路由常量 parity 门禁Accepted
0024健康探针 liveness / readiness 分离Accepted
0025定时任务单实例锁与 bootstrap 归属Accepted
0026请求签名原文跨端统一Accepted
0027数据权限行级设计(六档 + PG RLS 双保险)Accepted

全部 27 篇目前都是 Accepted —— 这不是「列没意义」,而是这个仓到目前没有被否决或取代过的决策。 枚举的价值在于:真出现 Rejected / Superseded by ADR-NNNN 时,有地方放、也有门禁保证它被填对。

补充文档 ​

  • Zod vs class-validator 评估 —— 技术对比评估,记录为何暂不迁移 Zod。 它不是 ADR(无编号、无 **Status:**),所以不参与形态校验;结论已固化进 ADR 0016。

基于 MIT 许可发布