# ADR-005: Monorepo 布局(pnpm workspace,apps/server + apps/web + packages/shared) ## Status: Accepted (2026-08-26) ## Background 后端(Express)与前端(Vue3)同用 TypeScript,且有大量共享的领域类型(由 zod schema 经 `z.infer` 产出)、zod 数据 schema、统一响应 `ApiResponse`、错误码、枚举。若前后端各自复制一份类型定义,一旦模型变更会出现「同一模型多处漂移」,违背 spec-as-contract(契约应单点可寻址)。 ## Decision 采用 **pnpm workspace 单仓多包**: ``` gwy-exam/ ├── pnpm-workspace.yaml ├── package.json # 仅脚本 + workspace,不装业务依赖 ├── tsconfig.base.json # 共享 strict 编译基座 ├── apps/ │ ├── server/ # Express 5 + TS + ESM │ └── web/ # Vue3 + Vite + TS └── packages/ └── shared/ # 领域类型 + zod schema + ApiResponse + 枚举 + 常量 ``` 类型契约、zod schema、`ApiResponse`、错误码联合全部放 `packages/shared`,`apps/server` 与 `apps/web` 通过 workspace 引用共享。 ### 为什么选 monorepo 而非独立仓 1. 前后端同 JS/TS,`packages/shared` 是天然共享层,独立仓需额外发布/维护两份。 2. 契约类型单点维护,防漂移(spec-as-contract:契约可寻址、可审计)。 3. 一次 `pnpm install`、一次 CI,开发与验收路径单一。 ### 分层依赖(对照 code-organization.md) - `apps/server` 依赖 `packages/shared`(向下)。 - `apps/web` 依赖 `packages/shared`(向下)。 - `packages/shared` **不依赖任何 app**,保持纯净、可被任何一方 import 而无环。 ## Consequences - 正面:类型单一来源、契约一致、前后端并行开发不踩、一次安装。 - 负面:单仓体积稍大;`packages/shared` 须保持无业务、无 app 依赖(否则环),需纪律约束。 - 权衡:个人工具规模下,monorepo 的成本(配置 pnpm workspace + 共享 tsconfig)远低于收益(类型一致性)。若未来拆出多应用,可无损演进。 ## Related ADRs - ADR-001(Express)/ ADR-002(Vue+Vite) - ADR-004(全 TS strict — 与 shared 类型强绑定) - ADR-006(垂直切片 — shared 作为单一契约源的承载层)