Files
color_full/docs/adr/0003-four-layer-module-architecture.md

97 lines
4.4 KiB
Markdown
Raw Permalink Normal View History

# ADR-0003: 后端四层模块化架构
> **状态**: 已决议
> **日期**: 2026-05-21
> **父决策**: ADR-0001整体技术栈
> **决策者**: 架构评审
---
## 上下文
项目初期路由层直接操作 Prisma业务逻辑与 HTTP 适配耦合。随着模块增长8 个领域模块29 个 API 端点),缺乏分层导致:
- 业务逻辑无法脱离 HTTP 环境做单元测试
- Prisma 调用散落在 8 个路由文件中,查询逻辑难以复用
- 横切关注点(日志、审计、错误处理)没有统一注入点
- 单文件超长formulas 路由 295 行)难以维护
需对后端架构做分层设计,支持企业级扩展。
---
## 决策
**选择四层模块化架构**Route → Service → Repository → Prisma按领域模块聚合文件。
### 目录结构
```
src/
├── modules/
│ ├── formulas/
│ │ ├── formulas.route.ts # Fastify 路由注册 + 参数提取
│ │ ├── formulas.service.ts # 纯业务逻辑 + 审计埋点
│ │ ├── formulas.repository.ts # Prisma 数据访问
│ │ ├── formulas.schema.ts # Zod 验证 schema
│ │ └── formulas.test.ts # 集成测试
│ ├── ingredients/ # 同上
│ ├── projects/ # 同上
│ ├── color/ # 同上
│ ├── ai/ # 同上
│ ├── auth/ # 同上
│ ├── config/ # 同上
│ └── health/ # 同上
└── shared/ # 跨模块共享
├── errors/ # AppError 异常体系
├── logging/ # AsyncLocalStorage 上下文
├── middleware/ # RBAC / Ownership 中间件
├── metrics/ # Prometheus 指标
└── audit/ # 审计服务
```
### 对比方案
| 方案 | 优势 | 劣势 | 结论 |
|------|------|------|------|
| **四层模块化(选)** | 按领域聚合改动一个功能不需跨目录跳转Service/Repository 可独立单元测试;横切关注点通过 shared/ 统一注入 | 小模块(如 health文件数多 | ✅ |
| 三层Route→Service→Prisma | 简单直接 | Service 与持久化耦合,不含 Repository 则 Prisma mock 困难 | ❌ |
| 按层分目录routes/ / services/ / repositories/ | 层边界清晰 | 同功能文件分散在 4 个目录,开发时频繁切换 | ❌ |
| Clean/六边形 | 核心领域零框架依赖 | 过度工程化;当前仅 Web 端,无需端口-适配器抽象 | ❌ |
---
## 层职责
| 层 | 职责 | 依赖 | 测试方式 |
|----|------|------|----------|
| **Route** | Fastify 注册、参数提取req→纯数据、preHandler 挂载 | Controller/Service + Zod | `app.inject()` 集成测试 |
| **Service** | 纯业务逻辑、审计埋点、百分比验证 | Repository + AuditService | 单元测试mock repository |
| **Repository** | Prisma 查询封装、事务管理 | Prisma | Testcontainers 集成测试 |
| **Schema** | Zod 验证定义、TypeScript 类型导出 | Zod | 不需要测试(声明式) |
---
## 横切关注点
| 关注点 | 实现方式 | 注入点 |
|--------|----------|--------|
| **认证** | JWT preHandlerapp.ts 全局) | onRequest |
| **授权** | `requireRole()` / `requireFormulaOwnership()` | Route preHandler |
| **错误处理** | AppError 子类ValidationError/NotFoundError 等) | 全局 `setErrorHandler` |
| **日志** | pino 结构化日志 + AsyncLocalStorage context | `app.log.child()` |
| **审计** | AuditServicepino 输出action/resource/userId | Service 层显式调用 |
| **指标** | prom-clienthttp_requests_total, app_errors_total, ai_requests_total | 全局 handler / AI Service |
| **输入校验** | `validateOrReply()` — Zod 解析,失败时 400 | Route 层 |
| **API 文档** | `@fastify/swagger` + `zod-to-json-schema` | Route schema 定义 |
---
## 后果
- 新增模块需创建 4 个文件route/service/repository/schema模板明确
- Service 和 Repository 可脱离 HTTP 环境做纯函数测试
- 跨模块共享逻辑必须放在 `shared/` 下,不能在模块间直接 import
- 所有错误必须使用 AppError 子类,不可裸抛 `new Error()`
- 模块测试文件与源文件同目录vitest `include: ['src/**/*.test.ts']` 自动发现