16 KiB
PRD — App Factory / Config Unification 完整优化清单
背景
ephron.ren 当前已经完成了一轮 app factory / config shared 化重构,远端相关主线提交为:
14484ca— Add shared modules: app factory, config base, error handlers with tests6d94b08— Refactor all apps to use shared config and app factory378a4e5— Update home pages route and add test templates
现状不是“完全没做”,而是:
- shared 基础设施已经建立
- 五个服务已经基本接入 shared app/config 骨架
- 但整体状态仍停留在“完成了第一轮统一”,还没达到“结构稳定、依赖完整、验证闭环、后续不易漂移”的完成态
因此这份 PRD 不再按 Batch 1 / Batch 2 / follow-up 拆分,而是直接给出 从当前状态到完成态的完整优化清单。
这份文档的目标是:开发只看这一份,就知道哪些必须改、哪些建议改、哪些不要顺手乱改,以及最后怎么验收。
总体结论
当前重构方向是对的,但还差四类关键补完:
-
依赖契约没补齐
shared.app_factory已把slowapi变成公共硬依赖- 但测试/安装链路没有完全同步,导致服务入口级测试会直接 import 失败
-
导入边界不稳定
main.py/config.py乃至 route/service/test 里仍保留大量sys.path.insert(...)- 说明 shared 化了逻辑,但没收住模块边界
-
admin 路由守卫仍是服务内私有实现
home已有实际 guard- 但 guard 骨架没 shared 化,未来其他服务继续扩展时很容易复制漂移
-
验证层只证明了 shared 能跑,没充分证明服务入口能跑
- 当前 shared tests 通过,不代表五个服务入口都可稳定导入/装配
优化目标
完成本清单后,应该达到以下状态:
- 五个服务的 app 创建与 config 读取,已经统一且可维护
- shared app factory 的依赖在开发、测试、部署三条链路中一致成立
- Batch 2 涉及的 10 个
main.py/config.py文件不再各自复制路径注入模板 home的 admin 守卫公共骨架进入 shared 层,后续可供其他服务复用/health只由 shared app factory 统一归口- 测试不仅覆盖 shared 合同,还覆盖服务入口装配合同
- 改完后可以明确说:这轮统一已经从“代码抽取”升级为“工程约束已建立”
非目标
本轮不要做以下事情:
- 不重写整站认证体系
- 不全量重构所有 route/service/test 的导入结构
- 不一次性把所有 auth/token 逻辑全部抽进 shared
- 不改业务权限模型
- 不改 service API 设计
- 不迁移仓库目录结构
- 不强制一步到位改成完整 package 化(如全面 pyproject/installable layout)
- 不顺手做与本次重构无关的业务修复
原则很简单:只修 app/config 统一后暴露出来的结构性缺口,不把这轮工作膨胀成全仓重构。
一、必须修改(P0 / P1)
P0-1. 补齐 slowapi 依赖契约
问题
shared.app_factory.py 已直接依赖:
slowapi._rate_limit_exceeded_handlerslowapi.errors.RateLimitExceeded
这意味着:
- 只要服务入口用 shared factory
slowapi就不再是“某服务可选依赖”- 而是整个服务启动路径的公共硬依赖
复审中的实际验证结果也说明了这个问题:
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
在 collection 阶段报:
ModuleNotFoundError: No module named 'slowapi'
必须改什么
- 检查项目实际依赖入口
- 根依赖文件
- 测试依赖入口
- 部署安装入口
- 保证
slowapi在这三条链路里都被安装 - 如果存在多个 requirements 文件,必须统一,不允许某条链路漏掉
- 若 CI 有单独安装脚本,也必须同步更新
验收标准
以下命令在标准开发环境中必须通过:
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
P0-2. /health 必须只保留 shared app factory 单一归口
问题
app factory 统一后的设计目标之一,就是 /health 由 shared factory 统一注册。
这件事必须做到“单一所有权”,否则后续一旦 health 响应结构升级,很容易出现某个服务还是旧实现。
当前重点确认点:
home/src/routes/pages.py不应再重复定义/health- 其他服务也不应在页面路由层残留重复 health endpoint
必须改什么
- 全仓检查
/health定义位置 - 保留 shared app factory 中的统一实现
- 删除各服务 pages/routes 中的重复实现
- 保证最终每个服务的
/health都来自同一装配路径
验收标准
- 服务层页面路由文件中不再重复定义
/health GET /health行为仍与现有外部契约兼容- 相关 shared / entrypoint tests 通过
P1-1. 收敛 Batch 2 目标文件中的 sys.path.insert(...) 残留
问题
本轮 shared 化后,以下 10 个核心文件本身仍保留路径注入模板:
auth/src/config.pyblog/src/config.pycanvas/src/config.pyprompt/src/config.pyhome/src/config.pyauth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.py
这说明当前统一只是“逻辑 shared 化”,不是“导入边界稳定化”。
必须改什么
本轮至少把这 10 个文件收住,不要求一次性清理全仓。
推荐两种方案里选一种:
方案 A:抽统一 import bootstrap helper(推荐)
例如新增:
shared/imports.py
提供统一函数,例如:
def ensure_project_root(file_path: str, levels: int = 3) -> None: ...
让入口/配置文件统一调用 helper,不再各自复制:
project_root = Path(...)
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
方案 B:修正运行/测试约定,让入口文件不再自行改 path
如果当前项目运行方式允许,也可以通过统一 cwd / pytest path / uvicorn 启动约定来消除核心入口内的 path hack。
但从风险和改造成本看,本轮更推荐方案 A。
最低要求
至少保证上面列出的 10 个文件不再各自手写重复模板,哪怕先统一到一个 helper。
验收标准
- 10 个核心文件中不再散落重复的 path 注入模板
- 改完后这 10 个文件仍然可导入
- 不引入新的循环依赖
P1-2. 将 home/admin 路由守卫的公共骨架提取到 shared
问题
当前 home/src/routes/admin.py 实际上已经有明确的服务端路由守卫:
- 未登录跳转 auth 登录页
- 带 redirect 返回
- 已登录但无权限则拒绝/跳转
- service token 禁止访问 home admin
这说明现在 有 guard,但它仍然是 home 服务内的私有实现。问题不在“有没有守卫”,而在“守卫骨架没有统一”。
如果未来 blog/canvas/prompt 继续加强 admin 规则,极容易再次复制 _require_auth(...) 模式,造成:
- 登录跳转策略漂移
- redirect 处理方式漂移
- service token admin 访问策略漂移
必须改什么
- 在
shared/新增 admin guard helper(命名可调整) - helper 至少负责这三件事:
- 未登录时的登录跳转 URL 拼装
- service token 是否允许访问 admin 的统一策略
- 权限失败时的统一返回约定
home/src/routes/admin.py改为消费 shared helper- 先落地 home,不要求本轮同步改完所有服务 admin
不需要在本轮做的事
- 不要求把所有业务权限判断都抽进去
- 不要求统一所有服务特有的资源级权限逻辑
- 不要求一次性重写 blog/canvas/prompt 的 admin 认证结构
验收标准
home/src/routes/admin.py不再保留大段内联 guard 骨架模板- shared helper 能表达登录跳转 / redirect / service token admin 禁入策略
- home 现有 admin 行为无回归
P1-3. 补服务入口装配级合同测试
问题
当前 shared 测试通过,能证明:
- shared config base 基本可用
- shared error handlers 基本可用
- shared app factory 基本可用
但这不等于五个真实服务入口都还能导入、装配、挂 router、提供 /health。
现在缺的是一类非常轻量但非常关键的测试:服务入口装配合同测试。
必须改什么
新增一组轻量测试,重点不是测业务,而是测“服务入口仍站得住”。
建议覆盖:
- 五个服务入口都可导入
auth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.py
- 导入后存在
app app.routes中包含/health- docs 开关仍符合 dev/prod 预期
- static mount / router 注册没有在统一重构中丢失
验收标准
新增测试能在这些情况给出明确失败信号:
- shared factory 依赖缺失
- 入口导入断裂
/health丢失- app factory 装配未生效
二、建议修改(P2)
这些不是“本轮不改就一定阻断”的问题,但建议一起纳入开发检查表。
P2-1. 统一依赖声明与文档说明
建议改什么
- 在依赖文件中明确标注 shared app factory 的新增公共依赖
- 若项目有 README / 开发说明,补一句:
- 使用 shared app factory 的服务,需要安装公共依赖集
- 若有部署脚本或 bootstrap 脚本,检查是否与 requirements 同步
目的
避免开发者只看代码不看变更背景,导致本地能跑 shared tests、却跑不动服务 tests。
P2-2. 为 ENV / ENVIRONMENT 兼容关系补显式测试
背景
当前 home 仍兼容历史 ENVIRONMENT,这是对的,但兼容行为需要被明确测试下来,否则后面很容易被“顺手清理”掉。
建议增加的断言
至少覆盖:
- 只设置
ENVIRONMENT=development时,home仍进入开发模式 - 同时设置
ENV=production与ENVIRONMENT=development时,以ENV为准 - 未设置时默认回落到
production
目的
把“历史兼容”从口头约定变成测试契约。
P2-3. 统一 config summary 输出风格,但不要统一业务字段含义
建议改什么
- 保持 summary 打印格式统一
- 但不要为了“字段看起来一样”而强行让各服务展示完全同一组字段
原则
- 统一的是打印骨架
- 不是每个服务必须展示完全一样的业务字段
例如:
blog仍应展示CONTENT_DIR/CACHE_DIRhome仍应展示AUTH_BASE_URL/SERVICE_PORT
目的
避免“形式统一”侵入业务语义。
P2-4. 为后续全仓导入治理保留清单,但不要本轮扩大范围
已确认存在的后续技术债
除了 10 个核心文件外,route/service/test 层仍有较多 sys.path.insert(...)。
建议处理方式
- 本轮只在 PRD 中登记为后续项
- 不要在这轮顺手扩展到全仓清理
原因
这类改动跨面太大,容易把“当前可收敛的重构收尾”演变成“无法控风险的大扫除”。
三、明确不要修改的内容
以下内容在本轮必须明确禁止“顺手改”:
-
不要重写业务权限模型
- 例如 owner/admin/user 的权限体系
- 不属于 app/config 统一收尾范围
-
不要把所有 auth/token 校验逻辑一次性抽到 shared
- 当前只要求抽 admin guard 公共骨架
- 不是全站认证大重构
-
不要一口气清理所有
sys.path.insert(...)- 只收缩 Batch 2 涉及的核心入口/配置文件
- 其余留待后续技术债专项
-
不要改 service API / 路由外部契约
- URL 不变
- 响应形态不因为这轮统一而改变
-
不要顺手改目录结构
- 不迁移为全量 package layout
- 不移动服务目录
-
不要把 config 层业务逻辑抽进 shared
- 例如 blog 的缓存目录创建
- canvas 的 content 目录自动创建
- 这些仍应由各服务自己负责
四、建议实施顺序
虽然这份文档不再按阶段写,但实际落地仍建议按风险从低到高推进:
1. 先补依赖契约
- 先解决
slowapi安装链路 - 否则后续很多入口测试都没法真实验证
2. 再补入口合同测试
- 先建立“导入/装配级失败能被看见”的机制
- 避免边改边盲飞
3. 再收敛 10 个核心文件的 path hack
- 控制范围,不碰全仓
4. 再抽 home/admin guard 骨架
- 这是结构优化,不是依赖修复,放在后面更稳
5. 最后全量回归
- shared tests
- auth smoke
- home tests
- 新增 entrypoint tests
五、建议涉及文件
必改文件
shared/app_factory.pyshared/config_base.py(如需补 helper / 兼容逻辑 / 测试契约)auth/src/main.pyblog/src/main.pycanvas/src/main.pyprompt/src/main.pyhome/src/main.pyauth/src/config.pyblog/src/config.pycanvas/src/config.pyprompt/src/config.pyhome/src/config.py- 依赖声明文件(按项目实际结构)
建议新增文件
shared/imports.py或等价 helpershared/admin_guard.py或等价 helpertests/test_service_entrypoints.py或等价入口合同测试文件
只观察,不要求本轮修改
home/src/services/auth.py- 其他 route/service/test 中的历史
sys.path.insert(...) - 任何 service API / 业务 handler 的功能逻辑
六、完整验收命令
至少执行并记录以下验证:
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 guard helper,再补:
python -m pytest home/tests -q
如果有 shared 相关新增单测,也一并跑:
python -m pytest tests/test_shared_config_base.py tests/test_error_handlers.py tests/test_app_factory.py -q
七、完成定义
满足以下全部条件,才算这轮优化真正完成:
slowapi在开发、测试、部署链路中都已被视为公共硬依赖- 服务入口级测试不再因
slowapi缺失而 import 失败 /health已统一归口到 shared app factory- Batch 2 涉及的 10 个核心
main.py/config.py文件不再各自复制 path 注入模板,或至少统一到同一 helper home的 admin 路由守卫公共骨架已进入 shared 层- 新增服务入口装配合同测试,并实际通过
- shared 层测试 + auth smoke + home 相关测试均通过
- 外部行为未发生非预期变化:
- URL 不变
- docs_url 行为兼容
- static mount 行为兼容
- 404/500 页面行为兼容
八、一句话给开发的执行原则
这轮工作的本质不是“继续抽公共代码”,而是:
把已经做完的 shared 化,补成一套真正可依赖、可验证、可持续的工程约束。
判断标准也很明确:
- 不是“代码看起来更整洁了”
- 而是“入口真的能导入、依赖真的齐、guard 不再乱飘、测试真的能兜住回归”
做到这一点,这轮 app factory / config unification 才算真正收口。