Files
ephron-ren-prd/prd-app-factory-config-unification-batch2-followup.md
2026-05-16 15:06:53 +08:00

12 KiB
Raw Blame History

PRD — App Factory / Config Unification Batch 2 Follow-up

背景

基于 origin/main 最新代码的完整复审Batch 2 已经完成了两项重要统一:

  1. 五个服务入口 main.py 已统一切到 shared.app_factory.create_service_app(...)
  2. 五个服务配置 config.py 已基本统一切到 shared.config_base

对应远端提交主线为:

  • 14484ca Add shared modules: app factory, config base, error handlers with tests
  • 6d94b08 Refactor all apps to use shared config and app factory
  • 378a4e5 Update home pages route and add test templates

这说明 Batch 2 的方向是正确的,且公共启动逻辑已经完成第一轮收敛。

但完整复审后,当前状态还不能定义为“重构完成”。现有实现仍存在三类结构性残留:

  • 入口层和业务层仍大量依赖 sys.path.insert(...) 做导入兜底
  • shared.app_factory 已把 slowapi 变成统一启动依赖,但测试/安装契约没有同步补齐
  • home/admin 的认证守卫仍是服务内私有实现,没有进一步抽象为 shared guard helper后续容易再次漂移

因此需要补一轮 follow-up 修正,目标不是继续大规模抽象,而是把 Batch 2 从“表面统一”推进到“结构上站稳”。


本轮目标

  1. 消除本轮重构后最显著的导入技术债,减少 sys.path.insert(...) 残留
  2. 补齐 slowapi 的依赖契约与测试契约,确保 shared factory 在开发、测试、部署三条链路上一致
  3. 为 home/admin 当前的路由守卫模式定义统一抽象边界,避免未来各服务重复复制 _require_auth(...)
  4. 补齐回归验证标准,让“通过”不再只表示 shared 单测通过,而是关键服务入口也能被实际导入/启动

非目标

本轮不做以下内容:

  • 不重写整站认证体系
  • 不把所有服务的 auth/token 校验一次性全量抽到 shared
  • 不强行改成 Python package 完整安装式布局(如一次性引入 pyproject + editable install 作为唯一导入方案)
  • 不改动业务权限模型本身
  • 不为所有历史测试做全面 fixture 重构

本轮只处理 Batch 2 复审中已经暴露、且会持续影响后续维护的最小必要问题。


发现的问题

P0. shared factory 统一后,slowapi 变成强依赖,但测试/环境契约未同步

现象

复审中执行:

python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q

结果通过:

13 passed

但执行 auth 侧 smoke tests

python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q

结果在 collection 阶段失败:

ModuleNotFoundError: No module named 'slowapi'

根因

shared.app_factory.py 直接导入:

  • from slowapi import _rate_limit_exceeded_handler
  • from slowapi.errors import RateLimitExceeded

这意味着一旦服务入口统一切到 shared factoryslowapi 就不再是某些服务“可选依赖”,而是所有服务公共启动路径的硬依赖。

但是当前测试环境或依赖清单没有保证这一点,导致:

  • shared 层合同测试可以过
  • 真实服务入口测试却无法 import app

这会制造“重构似乎完成,但服务级 smoke 根本没跑”的假阳性。

需要修改

  1. 检查并统一项目依赖声明,确保 slowapi 出现在实际安装入口中
    • requirements*.txt / 服务依赖清单 / 部署安装脚本中必须一致
  2. 如果项目采用多入口安装方式,至少保证:
    • 本地开发安装能拿到 slowapi
    • CI / 测试安装能拿到 slowapi
    • 生产部署安装能拿到 slowapi
  3. 补一条最小 smoke 验证:
    • 能成功 import auth/src/main.py 对应 app
    • 能成功 import home/src/main.py 对应 app

验收标准

以下命令在标准开发环境中必须通过:

python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q

不允许再出现 ModuleNotFoundError: slowapi


P1. sys.path.insert(...) 残留过多,说明 shared 化尚未形成稳定导入边界

现象

本轮复审中,目标文件本身仍保留路径注入:

  • auth/src/config.py
  • blog/src/config.py
  • canvas/src/config.py
  • prompt/src/config.py
  • home/src/config.py
  • auth/src/main.py
  • blog/src/main.py
  • canvas/src/main.py
  • prompt/src/main.py
  • home/src/main.py

此外,搜索结果表明更多 route / service / test 文件中也仍在重复同一模式。

问题

这说明当前 shared 重构虽然抽走了公共逻辑,但没有真正建立清晰的模块导入边界。直接后果有三个:

  1. 各服务仍默认依赖“脚本式运行 + 手动改 sys.path”
  2. 测试文件需要反复插 path 才能 import src.*
  3. 后续任何目录结构变化,都会在多个服务同时引发脆弱导入问题

本轮建议策略

不要一口气清理全仓所有 sys.path.insert(...),而是先做“核心路径收缩”:

第一阶段:只处理 Batch 2 涉及的入口与配置

目标:

  • 让五个 main.py
  • 五个 config.py

尽量不再各自复制路径注入模板。

推荐方案二选一:

方案 A抽一个 shared import bootstrap helper

  • 新建如 shared/imports.py 或等价模块
  • 提供统一函数,例如:
    • ensure_project_root(__file__, levels=3)
  • 所有入口/配置文件统一调用这一 helper而不是手写 Path(...); if str(...) not in sys.path: sys.path.insert(...)

方案 B明确项目运行约定减少入口内 path hack

  • 如果现有运行方式允许,以仓库根目录为 working directory 运行 uvicorn / pytest
  • 配合测试配置修正 import path
  • 让入口文件不再自己改 sys.path

本轮更推荐 方案 A,原因:

  • 改动小
  • 风险低
  • 不要求一次性重建整个包结构
  • 能先消掉最显眼的重复模板
第二阶段:只在 follow-up 里列出,不要求本轮全做
  • route 层残留 sys.path.insert(...)
  • service 层残留 sys.path.insert(...)
  • test 层残留 sys.path.insert(...)

这部分可以作为下一轮技术债治理,不在本轮强制清仓。

验收标准

至少以下 10 个文件不再各自手写重复的 sys.path.insert(...) 模板,或改为统一 helper

  • auth/src/config.py
  • blog/src/config.py
  • canvas/src/config.py
  • prompt/src/config.py
  • home/src/config.py
  • auth/src/main.py
  • blog/src/main.py
  • canvas/src/main.py
  • prompt/src/main.py
  • home/src/main.py

并补一条测试/检查,验证这些入口仍可被正常导入。


P1. home/admin 路由守卫真实存在,但仍是私有实现,后续易漂移

现象

home/src/routes/admin.py 目前已经具备明确的服务端守卫逻辑:

  • _require_auth(...) 负责 cookie 用户校验
  • 未登录跳转到 auth 登录页,并携带 redirect
  • 已登录但无权限时拒绝或回首页
  • service token 明确禁止访问 home admin

这说明 ephron.ren 现状是:

  • 前台公开页无全局 guard
  • 管理后台路由有服务端 guard

这一方向本身是合理的。

问题

现在的 guard 逻辑仍被实现为 home 服务内部私有函数,风险在于:

  1. 如果 blog/canvas/prompt 后续继续补 admin 规则,很容易各自再复制一份 _require_auth(...)
  2. 登录跳转、redirect 处理、service token 禁入策略可能在不同服务间漂移
  3. 审计时很难快速确认“所有 admin 路由是否遵循同一守卫契约”

本轮要求

本轮不要求把所有服务的鉴权都重构完,但至少要把“守卫抽象边界”定下来。

推荐最小改法:

  1. shared/ 新增 admin guard helper命名可调整
    • 负责未登录重定向的通用拼装
    • 负责 service token 是否允许访问 admin 的统一策略
    • 负责权限失败的统一响应约定
  2. home/src/routes/admin.py 改为消费 shared helper而不是继续把完整策略内嵌在文件内部
  3. 先以 home 为落点,其他服务后续增量迁移

范围边界

本轮 shared guard helper 不要求覆盖:

  • 所有业务权限判断细节
  • 各服务特有的资源级校验
  • 所有服务 token 行为差异

只需要先统一“admin 入口守卫”的公共骨架。

验收标准

  • home/src/routes/admin.py 中不再保留大段内联守卫模板代码
  • 登录跳转、redirect、service token 禁止访问 admin 的公共逻辑进入 shared helper
  • 现有 home admin 行为保持不变

P2. 当前回归验证偏 shared 层,缺少“服务入口可导入”级别的合同测试

现象

当前已经有的 shared/contract 测试能证明:

  • 安全头策略存在
  • reuse contract 存在

但它们不能证明:

  • 五个服务入口都还能成功 import
  • shared app factory 的依赖在真实入口层可用
  • 各服务在统一 factory 下仍保持自己的 startup 责任

需要补齐

新增一组轻量合同测试,目标不是测业务,而是测“入口仍然站得住”:

建议至少覆盖:

  1. 五个服务入口可导入
    • auth/src/main.py
    • blog/src/main.py
    • canvas/src/main.py
    • prompt/src/main.py
    • home/src/main.py
  2. 导入后存在 app
  3. app.routes 中至少包含 /health
  4. docs 开关、static mount、router 注入保留各自行为(可做轻量断言)

验收标准

新增测试在标准环境下通过,并能在 shared factory 依赖缺失、入口装配损坏、health route 丢失时给出明确失败信号。


建议实施顺序

Task 1 — 补齐 slowapi 依赖契约

  • 检查根依赖声明
  • 检查测试环境安装入口
  • 补齐缺失项
  • 跑 auth smoke tests 验证

Task 2 — 收缩 Batch 2 目标文件中的 path hack

  • 先只处理五个 main.py
  • 再处理五个 config.py
  • 如果采用 shared helper保证 helper 本身足够轻量,不引入新的循环依赖

Task 3 — 提取 home admin guard 公共骨架

  • 保留当前行为不变
  • 只抽公共流程,不重写权限模型
  • 先让 home 成为第一个 shared guard 使用者

Task 4 — 补轻量入口合同测试

  • 测入口 import
  • /health
  • 测 shared factory 关键装配结果

建议涉及文件

必改

  • shared/app_factory.py
  • shared/config_base.py(如需辅助 import/helper可一并评估
  • auth/src/main.py
  • blog/src/main.py
  • canvas/src/main.py
  • prompt/src/main.py
  • home/src/main.py
  • auth/src/config.py
  • blog/src/config.py
  • canvas/src/config.py
  • prompt/src/config.py
  • home/src/config.py
  • 依赖声明文件(按实际项目结构)

可能新增

  • shared/imports.py 或等价 helper
  • shared/admin_guard.py 或等价 helper
  • 新的入口合同测试文件

本轮只观察,不强制修改

  • home/src/services/auth.py
  • 其他 route/service/test 中的历史 sys.path.insert(...)

验证命令

至少执行并记录以下命令:

cd /home/ubuntu/ephron.ren
git fetch origin
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q

如果新增了入口合同测试,再补:

python -m pytest tests/test_service_entrypoints.py -q

如有 home/admin 相关抽象变动,补对应最小验证:

python -m pytest home/tests -q

完成定义

满足以下条件才算本轮完成:

  1. slowapi 不再导致服务入口测试 import 失败
  2. Batch 2 涉及的 10 个入口/配置文件不再各自复制同一段 path 注入模板,或至少统一为同一个 helper
  3. home/admin 的公共守卫骨架进入 shared 层,当前行为无回归
  4. 新增轻量入口合同测试,能证明五个服务入口仍可正常装配
  5. 所有新增/修改测试通过

备注

本 PRD 的定位不是重新定义 Batch 2而是对 Batch 2 做“完整复审后的结构补强”。

换句话说,前一轮已经解决“多个服务启动方式不统一”的问题;这一轮要解决的是:

  • 统一之后是否真的可维护
  • 统一之后测试是否真能覆盖到入口层
  • 统一之后 guard/导入边界是否足够稳定

这轮做完,才能把 Batch 2 从“完成了一次重构”提升为“建立了可持续的基础设施约束”。