diff --git "a/docs/plans/13-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206\350\256\241\345\210\222.md" "b/docs/plans/13-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206\350\256\241\345\210\222.md" index 712a908..9160c7e 100644 --- "a/docs/plans/13-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206\350\256\241\345\210\222.md" +++ "b/docs/plans/13-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206\350\256\241\345\210\222.md" @@ -4,120 +4,121 @@ doc_no: "13" audience: - Dev - QA - - Ops -feature_area: backend-architecture-governance -purpose: "实现 PRD08 中的后端工程规范与架构治理,建立分层约束、测试门禁与文档治理。" +feature_area: backend-engineering-governance +purpose: "落地 PRD08 的后端工程治理要求:分层架构、shared 真相源、架构边界测试和复杂度控制。" canonical_path: "docs/plans/13-后端工程规范与架构治理计划.md" status: draft -version: "0.1.0" +version: "1.0.0" owner: "StephenQiu30" inputs: - "docs/prd/08-后端工程规范与架构治理.md" - "docs/design/01-个人自部署万能视频下载器技术设计.md" outputs: - - "后端工程规范与架构治理计划" + - "后端工程治理实施计划" triggers: - - "需要固化后端分层边界" - - "需要建立架构边界测试" + - "新增或重构后端模块" + - "调整 shared 常量或错误码" downstream: - - "openspec/specs/backend-layer-boundaries/spec.md" + - "后续涉及后端治理的实现计划" --- # PLAN13 后端工程规范与架构治理 ## 1. 背景 -视频下载后端已积累 router、service、core、schemas、models、worker 等多层职责,但缺少显式的依赖边界约束和架构治理规范。本计划承接 PRD08,将后端分层边界、依赖方向、单一事实来源和文档门禁固化下来。 +PRD08 定义了后端分层架构、SOLID 边界、shared 真相源和测试优先治理要求。PLAN13 将这些要求转化为可执行的渐进治理任务,不追求一次性重排全部代码,而是通过每次真实变更逐步收敛。 ## 2. 目标 -1. 固化后端分层架构边界和依赖方向。 -2. 建立架构边界测试防止回归。 -3. 创建 PRD08 和 PLAN13 文档补齐治理链路。 -4. 更新验证脚本检查文档完整性。 +1. 将 PRD08 的分层架构定义落到可验证的目录结构和依赖约束。 +2. 建立 shared 真相源,将分散的错误码和常量迁移到 `packages/shared/`。 +3. 建立架构边界测试,防止跨层依赖引入。 +4. 建立复杂度控制检查,防止单文件/单函数过度膨胀。 ## 3. 非目标 -- 不做大规模代码重构或目录迁移。 -- 不引入新的 `repositories/`、`sources/`、`tasks/` 目录。 -- 不改动前端工程规范。 -- 不改动 Worker 内部实现。 +- 不一次性重写全部已有模块。 +- 不引入 CI 自动化架构扫描工具(如 import-linter),留待后续评估。 +- 不覆盖 Worker 层的独立 repository 抽取(由后续 PLAN 处理)。 ## 4. 核心内容 -### 4.1 OpenSpec 变更 +### 4.1 Phase 1:Shared 真相源建立 -创建 `openspec/changes/backend-architecture-governance/` 目录,包含: +| 任务 | 文件 | 验证命令 | +| --- | --- | --- | +| 在 `packages/shared/` 创建 `codes.py`,定义所有 `failure_code` 常量 | `packages/shared/video_downloader_shared/codes.py` | `python -c "from video_downloader_shared.codes import *"` | +| 在 `packages/shared/` 创建 `platforms.py`,定义平台 ID 常量 | `packages/shared/video_downloader_shared/platforms.py` | `python -c "from video_downloader_shared.platforms import *"` | +| 在 `packages/shared/` 创建 `dto.py`,定义跨模块 DTO | `packages/shared/video_downloader_shared/dto.py` | `python -c "from video_downloader_shared.dto import *"` | +| 更新 `packages/shared/video_downloader_shared/__init__.py` 导出新模块 | `packages/shared/video_downloader_shared/__init__.py` | `python -c "import video_downloader_shared"` | -- `proposal.md`:后端工程规范治理提案 -- `design.md`:目录职责与架构设计 -- `specs/backend-layer-boundaries/spec.md`:分层边界规范 -- `tasks.md`:执行任务拆解 +### 4.2 Phase 2:分层目录骨架 -### 4.2 架构边界测试 +| 任务 | 文件 | 验证命令 | +| --- | --- | --- | +| 创建 `apps/api/app/domain/` 目录和 `__init__.py` | `apps/api/app/domain/__init__.py` | `python -c "from app.domain import *"` | +| 创建 `apps/api/app/repositories/` 目录和 `__init__.py` | `apps/api/app/repositories/__init__.py` | `python -c "from app.repositories import *"` | +| 创建 `apps/api/app/infrastructure/` 目录和 `__init__.py` | `apps/api/app/infrastructure/__init__.py` | `python -c "from app.infrastructure import *"` | +| 在各 `__init__.py` 中写入层级职责说明注释 | 各 `__init__.py` | 人工审阅 | -创建 `apps/api/tests/test_architecture_boundaries.py`,使用 AST 分析验证: +### 4.3 Phase 3:架构边界测试 -1. router 层不直接导入 `yt_dlp`、`minio`、`redis` 等外部 SDK。 -2. router 层不直接导入 `SessionLocal`(应通过 `Depends(get_db)`)。 -3. service 层不从 router 导入。 -4. core 层不从 service/router 导入。 +| 任务 | 文件 | 验证命令 | +| --- | --- | --- | +| 编写架构边界测试:验证 router 不直接引用 ORM | `apps/api/tests/test_architecture_boundaries.py` | `npm test -- test_architecture_boundaries` | +| 编写架构边界测试:验证 domain 不引用基础设施 | `apps/api/tests/test_architecture_boundaries.py` | `npm test -- test_architecture_boundaries` | +| 编写架构边界测试:验证 repository 不含业务逻辑 | `apps/api/tests/test_architecture_boundaries.py` | `npm test -- test_architecture_boundaries` | +| 编写 shared 常量完整性测试:验证无硬编码错误码 | `apps/api/tests/test_shared_constants.py` | `npm test -- test_shared_constants` | -### 4.3 文档更新 +### 4.4 Phase 4:复杂度控制 -1. 创建 `docs/prd/08-后端工程规范与架构治理.md` -2. 创建 `docs/plans/13-后端工程规范与架构治理计划.md` -3. 更新 `docs/README.md` 添加 PRD08 和 PLAN13 索引 -4. 更新 `scripts/validate-repository.sh` 检查 PRD08/PLAN13 存在性 +| 任务 | 文件 | 验证命令 | +| --- | --- | --- | +| 编写文件行数检查脚本 | `scripts/check_complexity.sh` | `bash scripts/check_complexity.sh` | +| 将复杂度检查集成到 `npm test` | `package.json` | `npm test` | +| 对超限文件标注拆分 TODO | 各超限文件 | 人工审阅 | -### 4.4 规范推广 +### 4.5 Phase 5:文档与 OpenSpec 同步 -将 `backend-layer-boundaries` spec 推广到 `openspec/specs/backend-layer-boundaries/spec.md` 作为基线。 +| 任务 | 文件 | 验证命令 | +| --- | --- | --- | +| 将 architecture-boundaries spec 推广到 `openspec/specs/` | `openspec/specs/architecture-boundaries/spec.md` | 人工审阅 | +| 将 shared-constants spec 推广到 `openspec/specs/` | `openspec/specs/shared-constants/spec.md` | 人工审阅 | +| 更新 `docs/prd/README.md` 添加 PRD08 | `docs/prd/README.md` | 人工审阅 | +| 更新 `docs/plans/README.md` 添加 PLAN13 | `docs/plans/README.md` | 人工审阅 | +| 更新 `scripts/validate-repository.sh` 添加新文件检查 | `scripts/validate-repository.sh` | `bash scripts/validate-repository.sh` | -## 5. 执行顺序 +## 5. 关联文档 -1. 创建 OpenSpec change artifacts(proposal、specs、design、tasks) -2. 创建 PRD08 和 PLAN13 文档 -3. 创建架构边界测试(红灯) -4. 更新文档和脚本 -5. 推广 spec 到基线 -6. 运行验证测试(绿灯) +### 5.1 输入文档 -## 6. 验证方式 +1. `docs/prd/08-后端工程规范与架构治理.md` — 分层架构、shared 真相源、测试治理要求 +2. `docs/design/01-个人自部署万能视频下载器技术设计.md` — §4.3 目录与模块边界 -### 6.1 自动化验证 +### 5.2 输出文档 -```bash -pytest apps/api/tests/test_openapi_contract.py -v -pytest apps/api/tests/test_api_contract.py -v -pytest apps/api/tests/test_worker_jobs.py -v -pytest apps/api/tests/test_task_endpoints.py -v -pytest apps/api/tests/test_architecture_boundaries.py -v -bash scripts/validate-repository.sh -``` +1. `openspec/specs/architecture-boundaries/spec.md` +2. `openspec/specs/shared-constants/spec.md` -### 6.2 手工验证 +## 6. 验收门禁 -- 复核 PRD08、PLAN13 与 `CLAUDE.md`、`WORKFLOW.md`、`docs/README.md` 的治理规则一致。 -- 检查 OpenSpec change 是否完成 verify + archive。 +- `packages/shared/video_downloader_shared/codes.py` 存在并可导入。 +- `apps/api/app/domain/`、`apps/api/app/repositories/`、`apps/api/app/infrastructure/` 目录存在。 +- 架构边界测试通过。 +- `bash scripts/check_complexity.sh` 通过。 +- `bash scripts/validate-repository.sh` 通过。 -## 7. 关联文档 +## 7. 风险与边界 -### 7.1 输入文档 +渐进治理意味着短期内新旧代码风格并存。Phase 1-2 建立骨架和真相源,Phase 3-4 建立防护,后续每次真实变更时逐步将已有代码迁移到治理结构中。不要求一次性迁移所有已有模块。 -1. `docs/prd/08-后端工程规范与架构治理.md` +## 8. 待确认问题 -### 7.2 输出文档 - -1. `openspec/specs/backend-layer-boundaries/spec.md` - -## 8. 风险与边界 - -- **风险**:AST 分析可能误报 → **缓解**:只检查实际的 import 节点。 -- **风险**:未来引入新目录时需要更新规范 → **缓解**:在 PRD08 中明确目录扩展流程。 +- Worker 层是否需要独立的 repository 抽取,还是复用 API 层 repository。 +- 是否需要在 CI 中添加 `scripts/check_complexity.sh` 作为必过检查。 ## 9. 变更记录 | 日期 | 作者 | 版本 | 变更说明 | | --- | --- | --- | --- | -| 2026-06-16 | StephenQiu30 | 0.1.0 | 初始化文档 | +| 2026-06-16 | StephenQiu30 | 1.0.0 | 新建 PLAN13:后端工程规范与架构治理 | diff --git a/docs/plans/README.md b/docs/plans/README.md index 325e276..6ac5152 100644 --- a/docs/plans/README.md +++ b/docs/plans/README.md @@ -2,6 +2,22 @@ 本目录存放执行计划类文档。 +## 文档列表 + +| 编号 | 标题 | 状态 | +| --- | --- | --- | +| PLAN01 | URL 协议与地址安全计划 | accepted | +| PLAN02 | 错误语义与日志脱敏计划 | accepted | +| PLAN03 | 平台画像注册与识别计划 | accepted | +| PLAN04 | 创建任务与状态查询计划 | accepted | +| PLAN05 | Worker 下载执行与失败分类计划 | accepted | +| PLAN06 | 取消重试与事件流计划 | accepted | +| PLAN07 | MinIO 对象归档计划 | accepted | +| PLAN08 | 预签名下载与过期清理计划 | accepted | +| PLAN09 | 本机开发与依赖复用计划 | accepted | +| PLAN10 | DockerCompose 部署与健康检查计划 | accepted | +| PLAN13 | 后端工程规范与架构治理计划 | draft | + ## 适合放入 1. 阶段计划与任务拆解。 diff --git "a/docs/prd/08-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206.md" "b/docs/prd/08-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206.md" index 6bc8b14..71a389d 100644 --- "a/docs/prd/08-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206.md" +++ "b/docs/prd/08-\345\220\216\347\253\257\345\267\245\347\250\213\350\247\204\350\214\203\344\270\216\346\236\266\346\236\204\346\262\273\347\220\206.md" @@ -5,23 +5,22 @@ audience: - PM - Dev - QA - - Ops -feature_area: backend-architecture-governance -purpose: "定义 FastAPI 后端工程边界、分层约束、测试门禁与文档治理,让视频下载后端可以长期按软件工程范式演进。" +feature_area: backend-engineering-governance +purpose: "定义 video-server 后端分层架构、SOLID 边界、shared 真相源、契约优先和测试优先治理要求。" canonical_path: "docs/prd/08-后端工程规范与架构治理.md" status: draft -version: "0.1.0" +version: "1.0.0" owner: "StephenQiu30" inputs: - - "docs/prd/03-异步下载任务主链路.md" + - "CLAUDE.md" - "docs/design/01-个人自部署万能视频下载器技术设计.md" outputs: - - "后端工程规范产品边界" - - "分层架构约束" + - "后端工程治理边界" + - "架构分层验收标准" triggers: - - "新增后端目录或模块" - - "调整 router/service/adapter 依赖关系" - - "引入新的外部 SDK 依赖" + - "新增或重构后端模块" + - "调整 router / schema / service / repository / worker 职责" + - "引入新的 shared 常量或错误码" downstream: - "docs/plans/13-后端工程规范与架构治理计划.md" --- @@ -30,105 +29,110 @@ downstream: ## 1. 背景 -视频下载后端已积累 router、service、core、schemas、models、worker 等多层职责。随着下载能力扩展(更多平台、更多处理流程),职责可能继续堆积到 router 或任务脚本中,导致耦合加深、测试困难、新贡献者无法快速定位职责归属。 +video-server 当前代码结构基本遵循 `routers → services → models` 的三层模式,但缺少正式的架构治理规范。随着功能增长,以下风险逐渐暴露: -当前后端目录结构: +1. `services/` 层同时承担业务编排和数据库读写,职责边界模糊。 +2. 错误码、状态枚举和常量分散在各模块,缺少 shared 真相源约束。 +3. 新增模块缺乏明确的层级归属和依赖方向约束,容易出现跨层依赖。 +4. 重构和扩展缺少测试门禁保护,容易引入回归。 -``` -apps/api/app/ -├── core/ # 配置、错误码、日志、安全、响应封装 -├── db/ # 数据库会话 -├── middleware/ # 请求上下文中间件 -├── models.py # SQLAlchemy 模型 -├── routers/ # API 路由层 -├── schemas.py # Pydantic 响应/请求模型 -├── services/ # 业务逻辑层 -├── utils/ # 工具函数 -└── main.py # FastAPI 应用入口 -``` +PRD08 为后续改造建立治理边界,使每次变更都可追溯到层级、依赖方向和测试门禁。 ## 2. 目标 -1. 明确 FastAPI 后端目录职责与模块边界。 -2. 约束 router/service/repository/adapter/task 的依赖方向。 -3. 确保共享状态、错误码、平台选择器、响应模型拥有单一事实来源。 -4. 建立架构边界测试防止跨层依赖回归。 -5. 将治理规则纳入 OpenSpec、PRD、Plan、Agent Review 归档闭环。 +1. 定义 router、schema、service、domain、repository、infrastructure、worker job 的职责和禁止事项。 +2. 确立 shared 包(`packages/shared/`)作为状态枚举、错误码和跨模块常量的唯一真相源。 +3. 要求新增或重构模块必须说明所属层级、依赖方向和测试门禁。 +4. 以渐进治理为原则:只约束真实变更和扩展点,不要求一次性重排全部代码。 ```gherkin -Given 后端代码库已存在 router、service、core 等分层 -When 开发者新增功能或修改现有模块 -Then 代码必须遵循分层依赖方向约束 -And 架构边界测试必须通过 -And 文档和规范必须同步更新 +Given 开发者需要新增或重构一个后端模块 +When 查阅 PRD08 +Then 能明确该模块所属层级、允许依赖方向、必须遵循的契约和测试要求 ``` ## 3. 非目标 -- 不做大规模代码重构或目录迁移。 -- 不引入新的 `repositories/`、`sources/`、`tasks/` 目录(当前代码规模不需要)。 -- 不改动前端工程规范。 -- 不改动 Worker 内部实现。 -- 不改动与当前下载链路无关的基础设施。 +- 不要求一次性重写全部目录结构。 +- 不引入大型治理平台或自动化扫描工具链。 +- 不覆盖前端工程规范。 +- 不为了形式抽象而引入无真实调用者的接口层。 ## 4. 核心内容 -### 4.1 分层职责定义 +### 4.1 分层架构定义 -| 层 | 目录 | 职责 | 允许依赖 | +| 层级 | 路径 | 职责 | 禁止事项 | | --- | --- | --- | --- | -| Router | `routers/` | 请求路由、参数校验、依赖注入、响应封装 | service、core、schemas、utils、adapter | -| Service | `services/` | 业务逻辑、并发检查、配额校验、状态流转 | models、core、utils、shared | -| Adapter | `services/download_adapter.py` | 外部 SDK 封装(yt-dlp、平台解析) | core、schemas、utils | -| Core | `core/` | 配置、错误码、日志、安全、响应封装 | 无业务层依赖 | -| Schema | `schemas.py` | Pydantic 请求/响应模型 | 无 | -| Model | `models.py` | SQLAlchemy 数据库模型 | 无 | -| Shared | `packages/shared/` | 跨项目共享状态枚举 | 无 | +| Router | `apps/api/app/routers/` | 协议层:接收请求、调用 service、返回响应 | 不含业务逻辑、不含数据库操作 | +| Schema | `apps/api/app/schemas.py` | Pydantic 契约:请求/响应模型定义 | 不含业务逻辑、不直接引用 ORM 模型 | +| Service | `apps/api/app/services/` | 业务编排:协调 domain、repository 和 infrastructure | 不直接写 SQL、不直接调用外部 API | +| Domain | `apps/api/app/domain/` | 纯业务规则:状态机、校验、计算 | 不引用数据库、不引用外部服务 | +| Repository | `apps/api/app/repositories/` | 数据库读写:封装 SQLAlchemy 操作 | 不含业务逻辑、只做 CRUD 和查询 | +| Infrastructure | `apps/api/app/infrastructure/` | 外部服务封装:Redis、MinIO、队列 | 不含业务逻辑、只做适配 | +| Worker Job | `apps/worker/worker/jobs/` | 异步任务入口:从队列拾取任务并编排执行 | 不直接操作数据库、通过 repository 更新状态 | ### 4.2 依赖方向约束 +```text +Router → Service → Domain + → Repository → ORM/DB + → Infrastructure → 外部服务 +Worker Job → Service → Domain + → Repository → ORM/DB + → Infrastructure → 外部服务 ``` -Router → Service → Model/Core/Shared -Router → Adapter → Core -Router → Core -Service → Model/Core/Shared -Adapter → Core -Core → (无业务层依赖) -``` -**禁止的依赖方向**: -- Service → Router(反向依赖) -- Core → Service/Router(反向依赖) -- Router → 外部 SDK 直接导入(yt_dlp、minio、redis) +**规则**: + +1. 上层可以依赖下层,下层不得依赖上层。 +2. 同层之间避免循环依赖。 +3. Domain 层是纯函数,不引用任何基础设施。 +4. Repository 和 Infrastructure 不互相依赖。 -### 4.3 单一事实来源 +### 4.3 Shared 真相源 -| 事实 | 定义位置 | 引用方式 | +`packages/shared/video_downloader_shared/` 是跨模块常量和枚举的唯一来源: + +| 内容 | 当前位置 | 目标位置 | | --- | --- | --- | -| TaskState | `packages/shared/video_downloader_shared/states.py` | `from video_downloader_shared.states import TaskState` | -| ErrorCode | `app/core/errors.py` | `from app.core.errors import ErrorCode` | -| 响应模型 | `app/schemas.py` | `from app.schemas import TaskRead, ...` | -| 配置 | `app/core/config.py` | `from app.core.config import get_settings` | +| `TaskState` 枚举 | `packages/shared/video_downloader_shared/states.py` | 保持不变 | +| `failure_code` 常量 | 分散在 services 和 worker | 迁移到 `shared/codes.py` | +| 平台 ID 常量 | 分散在 services 和 worker | 迁移到 `shared/platforms.py` | +| 跨模块 DTO | 各模块自定义 | 迁移到 `shared/dto.py` | + +**规则**: + +1. 新增错误码必须先在 `shared/codes.py` 定义,再在业务代码中引用。 +2. 新增状态枚举必须先在 `shared/states.py` 定义。 +3. 禁止在业务模块中硬编码错误码字符串或状态值。 + +### 4.4 契约优先 -### 4.4 架构边界测试 +1. API 请求/响应模型通过 Pydantic schema 定义,schema 变更先于实现。 +2. OpenAPI spec 通过 `scripts/export_openapi.py` 导出,作为前端集成的契约。 +3. 新增或修改 API 端点时,必须同步更新 schema 和 OpenAPI 导出。 -维护 `apps/api/tests/test_architecture_boundaries.py`,使用 AST 分析验证: +### 4.5 测试优先治理 -1. router 层不直接导入 `yt_dlp`、`minio`、`redis` 等外部 SDK。 -2. router 层不直接导入 `SessionLocal`(应通过 `Depends(get_db)`)。 -3. service 层不从 router 导入。 -4. core 层不从 service/router 导入。 +1. 新增模块必须有对应测试,测试覆盖核心业务规则和边界条件。 +2. 重构已有模块时,必须先有保护测试,再做结构变更。 +3. 测试不得为兼容旧行为、灰度双轨或临时兜底补写兼容性测试。 +4. 架构边界测试:验证依赖方向约束,防止跨层引用。 -### 4.5 大文件治理 +### 4.6 复杂度控制 -超过 200 行的 Python 文件 SHALL 按职责拆分,或在 PR 中给出保留理由。 +1. 单文件长期目标不超过 200 行;超过时按职责拆分。 +2. 单函数不超过 50 行;超过时提取子函数。 +3. 圈复杂度不超过 10;超过时重构条件分支。 +4. 新增模块必须在 PR 中说明所属层级和依赖方向。 ## 5. 关联文档 ### 5.1 输入文档 -1. `docs/prd/03-异步下载任务主链路.md` -2. `docs/design/01-个人自部署万能视频下载器技术设计.md` +1. `CLAUDE.md` — 提交规范、SDD/TDD/RAG 门禁、交付格式 +2. `docs/design/01-个人自部署万能视频下载器技术设计.md` — §4.3 目录与模块边界 ### 5.2 输出文档 @@ -136,27 +140,27 @@ Core → (无业务层依赖) ### 5.3 下游文档 -1. `openspec/specs/backend-layer-boundaries/spec.md` +1. 后续所有涉及后端新增或重构的 PLAN 文档 ## 6. 验收门禁 -- router 层不直接依赖外部下载 SDK、数据库细节或平台解析实现。 -- service/repository/adapter/task 职责边界清晰,并符合设计文档约束。 -- 共享状态、错误码、selector、响应模型拥有单一事实来源。 -- 架构边界测试通过。 -- `scripts/validate-repository.sh` 能覆盖 PRD08/PLAN13 存在性检查。 +- PRD08 路径存在并能独立说明后端工程治理边界。 +- 分层架构表格完整,每层有职责和禁止事项。 +- 依赖方向约束清晰,无歧义。 +- Shared 真相源规则明确。 +- 治理要求不要求一次性大规模重排所有代码。 ## 7. 风险与边界 -- **风险**:AST 分析可能误报注释中的 import 语句 → **缓解**:只检查实际的 import 节点。 -- **风险**:未来引入新目录时需要更新规范 → **缓解**:在 PRD08 中明确目录扩展流程。 +过度治理会拖慢迭代速度。PRD08 的原则是"治理只服务真实变更和扩展点"——如果一个模块没有被改动,不要求为它补充治理。渐进治理意味着短期内会存在新旧代码风格并存的情况,但每次变更都向治理目标收敛。 ## 8. 待确认问题 -- 无。 +- 是否需要为 Worker 层引入独立的 `worker/repositories/` 还是复用 API 层的 repository。 +- 是否需要在 CI 中添加自动化的架构边界检查(如 import-linter)。 ## 9. 变更记录 | 日期 | 作者 | 版本 | 变更说明 | | --- | --- | --- | --- | -| 2026-06-16 | StephenQiu30 | 0.1.0 | 初始化文档 | +| 2026-06-16 | StephenQiu30 | 1.0.0 | 新建 PRD08:后端工程规范与架构治理 | diff --git a/docs/prd/README.md b/docs/prd/README.md index 3cbb5c5..970c91c 100644 --- a/docs/prd/README.md +++ b/docs/prd/README.md @@ -22,6 +22,17 @@ PRD 的核心目的不是堆叠描述,而是让需求更清楚,让代码更 使用 `顺序编号-语义化标题.md`,例如 `01-解析入口与URL安全.md`。 +## 文档列表 + +| 编号 | 标题 | 状态 | +| --- | --- | --- | +| PRD01 | 解析入口与 URL 安全 | draft | +| PRD02 | 平台识别与平台画像 | draft | +| PRD03 | 异步下载任务主链路 | draft | +| PRD04 | MinIO 产物归档与下载交付 | draft | +| PRD05 | 自部署运行与环境复用 | draft | +| PRD08 | 后端工程规范与架构治理 | draft | + ## 推荐结构 PRD 应优先使用 `docs/TEMPLATE.md` 的 frontmatter,并按以下结构扩展: diff --git a/openspec/changes/backend-engineering-governance/design.md b/openspec/changes/backend-engineering-governance/design.md new file mode 100644 index 0000000..ff3db88 --- /dev/null +++ b/openspec/changes/backend-engineering-governance/design.md @@ -0,0 +1,94 @@ +## Context + +video-server 当前目录结构: + +```text +apps/api/app/ + routers/ — 协议层 + schemas.py — Pydantic 契约 + services/ — 业务编排 + 数据库读写(职责混合) + models.py — SQLAlchemy ORM 模型 + core/ — 配置、错误处理、日志、安全 + db/ — 数据库会话和迁移 + middleware/ — 请求上下文 + utils/ — 工具函数 + +apps/worker/worker/ + jobs/ — 异步任务入口 + domain.py — 业务规则 + download_runner.py — 下载执行 + ... + +packages/shared/video_downloader_shared/ + states.py — TaskState 枚举 +``` + +设计文档 `docs/design/01-个人自部署万能视频下载器技术设计.md` §4.3 已定义目标分层(routers/schemas/services/repositories/worker-jobs/worker-adapters/shared),但当前实现缺少 `domain/`、`repositories/`、`infrastructure/` 目录,`services/` 层混合了业务编排和数据库操作。 + +### 现有问题 + +1. `services/tasks.py` 直接操作 SQLAlchemy session,违反 service 层只做业务编排的原则。 +2. 错误码(`failure_code`)以字符串字面量分散在 `services/` 和 `worker/` 中,无统一定义。 +3. 平台 ID 常量分散在 `services/platforms.py` 和 `worker/` 中。 +4. 无架构边界测试,跨层依赖只能通过人工审阅发现。 + +## Goals / Non-Goals + +**Goals:** + +- 建立 domain、repository、infrastructure 分层目录骨架 +- 将错误码、平台 ID 和跨模块 DTO 迁移到 `packages/shared/` +- 建立架构边界测试,自动验证依赖方向约束 +- 建立复杂度控制检查脚本 +- 渐进治理:不要求一次性迁移所有已有代码 + +**Non-Goals:** + +- 不一次性重写 `services/` 层的所有数据库操作到 repository +- 不引入 CI 自动化架构扫描工具(import-linter 等) +- 不为 Worker 层创建独立的 repository(留待后续 PLAN) +- 不修改任何已有功能的行为 + +## Decisions + +### 1. Domain 层为纯函数目录 + +**选择**:`apps/api/app/domain/` 存放纯业务规则函数,不引用数据库或外部服务。 + +**理由**:Domain 层是业务逻辑的核心,纯函数易于测试和复用。当前 `services/` 中的校验逻辑(如 URL 校验、并发检查)可逐步迁移到 domain。 + +### 2. Repository 层封装 SQLAlchemy 操作 + +**选择**:`apps/api/app/repositories/` 存放数据库读写函数,每个函数对应一个原子操作。 + +**理由**:将数据库操作从 service 层分离,使 service 只做业务编排。Repository 函数接收 session 作为参数,不自行创建会话。 + +### 3. Infrastructure 层封装外部服务 + +**选择**:`apps/api/app/infrastructure/` 存放 Redis、MinIO、队列等外部服务的适配函数。 + +**理由**:将外部服务调用从 service 层分离,便于 mock 测试和替换实现。 + +### 4. Shared 包使用模块文件而非子包 + +**选择**:新增 `codes.py`、`platforms.py`、`dto.py` 作为 `packages/shared/video_downloader_shared/` 下的模块文件。 + +**理由**:当前 `states.py` 已经是单文件模式,保持一致。跨模块常量规模不大,不需要子包拆分。 + +### 5. 架构边界测试通过 import 分析实现 + +**选择**:`test_architecture_boundaries.py` 通过解析 Python 源码的 import 语句验证依赖方向。 + +**理由**:不需要引入额外依赖(如 import-linter),测试本身即文档。通过 AST 解析 import 比正则匹配更可靠。 + +### 6. 复杂度检查脚本使用简单行数统计 + +**选择**:`scripts/check_complexity.sh` 检查 Python 文件行数,超过 200 行的文件报 warning。 + +**理由**:行数是最简单可靠的复杂度代理指标。圈复杂度检查需要额外工具(如 radon),留待后续评估。 + +## Risks / Trade-offs + +- **风险**:渐进治理导致新旧代码风格并存 → **缓解**:架构边界测试确保新代码遵守约束,旧代码在被改动时逐步迁移 +- **风险**:domain 层初始为空,可能被质疑过度设计 → **缓解**:从 `services/` 中提取第一个纯业务函数时才创建 domain 模块 +- **风险**:shared 常量迁移可能引入循环依赖 → **缓解**:shared 包不依赖任何 app 代码,只被 app 代码单向依赖 diff --git a/openspec/changes/backend-engineering-governance/proposal.md b/openspec/changes/backend-engineering-governance/proposal.md new file mode 100644 index 0000000..25e4398 --- /dev/null +++ b/openspec/changes/backend-engineering-governance/proposal.md @@ -0,0 +1,32 @@ +## Why + +video-server 后端代码结构基本遵循 `routers → services → models` 三层模式,但缺少正式的架构治理规范。`services/` 层同时承担业务编排和数据库读写,错误码和常量分散在各模块,新增模块缺乏明确的层级归属和依赖方向约束。PRD08 已定义治理边界,需要通过 OpenSpec 将分层架构、shared 真相源和架构边界测试规范化,为后续改造提供可验证的治理基线。 + +## What Changes + +- 新增 OpenSpec spec `architecture-boundaries`:定义 router/schema/service/domain/repository/infrastructure/worker-job 的职责边界和依赖方向约束 +- 新增 OpenSpec spec `shared-constants`:定义 `packages/shared/` 作为错误码、状态枚举和跨模块常量的唯一真相源 +- 创建 `apps/api/app/domain/`、`apps/api/app/repositories/`、`apps/api/app/infrastructure/` 分层目录骨架 +- 创建 `packages/shared/video_downloader_shared/codes.py`、`platforms.py`、`dto.py` 真相源模块 +- 创建架构边界测试和 shared 常量完整性测试 +- 创建复杂度控制检查脚本 +- 将 specs 推广到 `openspec/specs/` 作为当前事实层 +- 更新 `scripts/validate-repository.sh` 添加新文件检查 + +## Capabilities + +### New Capabilities + +- `architecture-boundaries`: 后端分层架构职责边界和依赖方向约束规范 +- `shared-constants`: 跨模块常量和枚举作为唯一真相源的规范 + +### Modified Capabilities + +(无已有 spec 需要修改) + +## Impact + +- 受影响代码:`apps/api/app/`(新增 domain/repositories/infrastructure 目录)、`packages/shared/video_downloader_shared/`(新增 codes/platforms/dto 模块) +- 受影响测试:`apps/api/tests/test_architecture_boundaries.py`、`apps/api/tests/test_shared_constants.py` +- 受影响文档:`docs/prd/08-后端工程规范与架构治理.md`、`docs/plans/13-后端工程规范与架构治理计划.md`、`docs/prd/README.md`、`docs/plans/README.md` +- 受影响脚本:`scripts/validate-repository.sh`、`scripts/check_complexity.sh` diff --git a/openspec/changes/backend-engineering-governance/specs/architecture-boundaries/spec.md b/openspec/changes/backend-engineering-governance/specs/architecture-boundaries/spec.md new file mode 100644 index 0000000..af39a29 --- /dev/null +++ b/openspec/changes/backend-engineering-governance/specs/architecture-boundaries/spec.md @@ -0,0 +1,79 @@ +# Architecture Boundaries + +## Purpose + +定义 video-server 后端各层的职责边界、依赖方向约束和禁止事项。 + +## ADDED Requirements + +### Requirement: Router layer only handles protocol +Router 层 SHALL 只负责接收请求、调用 service 层和返回响应,不得包含业务逻辑或数据库操作。 + +#### Scenario: Router delegates to service +- **WHEN** API 接收请求 +- **THEN** Router 只做参数解析、认证校验和响应序列化,业务逻辑由 service 层处理 + +#### Scenario: Router does not import ORM models +- **WHEN** 检查 `apps/api/app/routers/` 的 import 语句 +- **THEN** 不得直接导入 `apps/api/app/models.py` 中的 ORM 模型 + +### Requirement: Schema layer only defines contracts +Schema 层 SHALL 只定义 Pydantic 请求/响应模型,不得包含业务逻辑或直接引用 ORM 模型。 + +#### Scenario: Schema uses Pydantic BaseModel +- **WHEN** 定义请求或响应模型 +- **THEN** 使用 Pydantic `BaseModel`,不继承 SQLAlchemy 模型 + +### Requirement: Service layer only orchestrates +Service 层 SHALL 只做业务编排:协调 domain、repository 和 infrastructure,不得直接写 SQL 或直接调用外部 API。 + +#### Scenario: Service delegates DB operations to repository +- **WHEN** Service 需要读写数据库 +- **THEN** 调用 repository 层函数,不直接操作 SQLAlchemy session + +#### Scenario: Service delegates external calls to infrastructure +- **WHEN** Service 需要调用 Redis、MinIO 或队列 +- **THEN** 调用 infrastructure 层函数,不直接使用外部客户端 + +### Requirement: Domain layer is pure business logic +Domain 层 SHALL 只包含纯业务规则:状态机、校验、计算。不得引用数据库、外部服务或框架组件。 + +#### Scenario: Domain function has no side effects +- **WHEN** 调用 domain 层函数 +- **THEN** 函数不访问数据库、不调用外部服务、不修改全局状态 + +### Requirement: Repository layer encapsulates DB operations +Repository 层 SHALL 封装所有 SQLAlchemy 数据库操作,每个函数对应一个原子 CRUD 或查询操作。 + +#### Scenario: Repository receives session as parameter +- **WHEN** 调用 repository 函数 +- **THEN** 函数接收 `db: Session` 作为参数,不自行创建会话 + +#### Scenario: Repository does not contain business logic +- **WHEN** 检查 repository 函数 +- **THEN** 函数只做数据存取,不包含条件分支、状态转换或业务规则 + +### Requirement: Infrastructure layer encapsulates external services +Infrastructure 层 SHALL 封装 Redis、MinIO、队列等外部服务的调用。 + +#### Scenario: Infrastructure is a thin adapter +- **WHEN** 调用 infrastructure 函数 +- **THEN** 函数只做协议转换和错误映射,不包含业务逻辑 + +### Requirement: Dependency direction is top-down +各层 SHALL 遵循从上到下的依赖方向:Router → Service → Domain / Repository / Infrastructure。下层不得依赖上层,同层不得循环依赖。 + +#### Scenario: No upward dependency +- **WHEN** 检查各层的 import 语句 +- **THEN** repository 不导入 service,domain 不导入 service 或 router,infrastructure 不导入 service + +#### Scenario: No circular dependency within same layer +- **WHEN** 检查同层模块的 import 语句 +- **THEN** 不存在 A 导入 B 同时 B 导入 A 的情况 + +### Requirement: Module declares layer and dependencies +新增或重构模块 SHALL 在模块顶部注释或 docstring 中说明所属层级和允许的依赖方向。 + +#### Scenario: New module has layer annotation +- **WHEN** 开发者新增一个后端模块 +- **THEN** 模块顶部包含 `# Layer: ` 注释或等效 docstring diff --git a/openspec/changes/backend-engineering-governance/specs/shared-constants/spec.md b/openspec/changes/backend-engineering-governance/specs/shared-constants/spec.md new file mode 100644 index 0000000..e098f60 --- /dev/null +++ b/openspec/changes/backend-engineering-governance/specs/shared-constants/spec.md @@ -0,0 +1,46 @@ +# Shared Constants + +## Purpose + +定义 `packages/shared/video_downloader_shared/` 作为跨模块常量、枚举和 DTO 的唯一真相源。 + +## ADDED Requirements + +### Requirement: Error codes defined in shared package +所有 `failure_code` 常量 SHALL 在 `packages/shared/video_downloader_shared/codes.py` 中统一定义,业务代码通过导入引用。 + +#### Scenario: New error code added to shared +- **WHEN** 开发者需要新增一个 `failure_code` +- **THEN** 先在 `codes.py` 中定义常量,再在业务代码中导入使用 + +#### Scenario: No hardcoded error code strings +- **WHEN** 检查 `apps/api/app/services/` 和 `apps/worker/worker/` 的代码 +- **THEN** 不存在硬编码的 `failure_code` 字符串字面量 + +### Requirement: Task state enum in shared package +任务状态枚举 SHALL 在 `packages/shared/video_downloader_shared/states.py` 中定义,业务代码通过导入引用。 + +#### Scenario: State transitions use shared enum +- **WHEN** 业务代码需要引用任务状态 +- **THEN** 导入 `TaskState` 枚举,不使用字符串字面量 + +### Requirement: Platform IDs defined in shared package +平台标识常量 SHALL 在 `packages/shared/video_downloader_shared/platforms.py` 中定义。 + +#### Scenario: Platform ID lookup uses shared constant +- **WHEN** 业务代码需要引用平台 ID +- **THEN** 导入 `platforms.py` 中的常量,不使用字符串字面量 + +### Requirement: Cross-module DTOs in shared package +跨 API 和 Worker 共享的数据传输对象 SHALL 在 `packages/shared/video_downloader_shared/dto.py` 中定义。 + +#### Scenario: DTO shared between API and Worker +- **WHEN** API 和 Worker 需要交换结构化数据 +- **THEN** 使用 `dto.py` 中定义的 DTO 类,不各自定义兼容结构 + +### Requirement: Shared package has no app dependencies +`packages/shared/` SHALL 不依赖 `apps/api/` 或 `apps/worker/` 中的任何模块。 + +#### Scenario: No upward import from shared +- **WHEN** 检查 `packages/shared/` 的 import 语句 +- **THEN** 不存在导入 `apps.api` 或 `apps.worker` 的语句 diff --git a/openspec/changes/backend-engineering-governance/tasks.md b/openspec/changes/backend-engineering-governance/tasks.md new file mode 100644 index 0000000..312516c --- /dev/null +++ b/openspec/changes/backend-engineering-governance/tasks.md @@ -0,0 +1,56 @@ +## 1. OpenSpec Change Artifacts + +- [x] 1.1 创建 `openspec/changes/backend-engineering-governance/proposal.md` +- [x] 1.2 创建 `openspec/changes/backend-engineering-governance/design.md` +- [x] 1.3 创建 `openspec/changes/backend-engineering-governance/specs/architecture-boundaries/spec.md` +- [x] 1.4 创建 `openspec/changes/backend-engineering-governance/specs/shared-constants/spec.md` +- [x] 1.5 创建 `openspec/changes/backend-engineering-governance/tasks.md` + +## 2. PRD 和 Plan 文档 + +- [x] 2.1 创建 `docs/prd/08-后端工程规范与架构治理.md` +- [x] 2.2 创建 `docs/plans/13-后端工程规范与架构治理计划.md` + +## 3. Shared 真相源 + +- [ ] 3.1 创建 `packages/shared/video_downloader_shared/codes.py`,定义所有 `failure_code` 常量 +- [ ] 3.2 创建 `packages/shared/video_downloader_shared/platforms.py`,定义平台 ID 常量 +- [ ] 3.3 创建 `packages/shared/video_downloader_shared/dto.py`,定义跨模块 DTO +- [ ] 3.4 更新 `packages/shared/video_downloader_shared/__init__.py` 导出新模块 + +## 4. 分层目录骨架 + +- [ ] 4.1 创建 `apps/api/app/domain/__init__.py`,写入层级职责说明 +- [ ] 4.2 创建 `apps/api/app/repositories/__init__.py`,写入层级职责说明 +- [ ] 4.3 创建 `apps/api/app/infrastructure/__init__.py`,写入层级职责说明 + +## 5. 架构边界测试 + +- [ ] 5.1 创建 `apps/api/tests/test_architecture_boundaries.py` + - 验证 router 不直接导入 ORM 模型 + - 验证 domain 不导入基础设施 + - 验证 repository 不含业务逻辑模式 +- [ ] 5.2 创建 `apps/api/tests/test_shared_constants.py` + - 验证 services 和 worker 中无硬编码错误码字符串 + +## 6. 复杂度控制 + +- [ ] 6.1 创建 `scripts/check_complexity.sh`,检查 Python 文件行数 +- [ ] 6.2 将复杂度检查集成到 `npm test` + +## 7. 文档与脚本更新 + +- [ ] 7.1 更新 `docs/prd/README.md` 添加 PRD08 条目 +- [ ] 7.2 更新 `docs/plans/README.md` 添加 PLAN13 条目 +- [ ] 7.3 更新 `scripts/validate-repository.sh` 添加新文件检查 + +## 8. 推广 Specs 到 Baseline + +- [ ] 8.1 推广 `architecture-boundaries` spec 到 `openspec/specs/architecture-boundaries/spec.md` +- [ ] 8.2 推广 `shared-constants` spec 到 `openspec/specs/shared-constants/spec.md` + +## 9. 验证 + +- [ ] 9.1 运行 `npm test` 确认所有测试通过 +- [ ] 9.2 运行 `bash scripts/validate-repository.sh` 确认仓库结构检查通过 +- [ ] 9.3 运行 `bash scripts/check_complexity.sh` 确认复杂度检查通过