Files
ephron-ren-prd/prd-app-factory-config-unification.md

11 KiB
Raw Blame History

PRD: App Factory + Config 统一重构PR1

背景

当前 ephron.ren 已经从单一 MVP 演进成多服务单仓结构,包含:

  • auth
  • blog
  • canvas
  • home
  • prompt
  • shared

但服务初始化与配置层仍保留大量早期复制实现,主要集中在:

  • 五个服务的 src/main.py
  • 五个服务的 src/config.py

这导致后续每次要统一安全头、health、docs、错误处理、配置约定时都需要多处重复修改并且很容易发生服务之间的行为漂移。

本 PRD 定义第一阶段重构:只收敛 app 初始化骨架与 config 读取骨架,不改业务逻辑


问题定义

1. main.py 高度重复

五个服务都重复做了以下工作:

  • 调用 validate_config()
  • 创建 FastAPI(...)
  • 安装 security headers
  • 创建并挂载 limiter
  • 挂载 static
  • 注册 routers
  • 注册 404/500 handler
  • 提供 /health

这种重复意味着:

  • 加一个通用能力要改 5 份
  • 某个服务漏改的概率很高
  • 测试也更难形成统一契约

2. config.py 既重复又不一致

当前配置层存在两个问题:

重复

各服务都各自实现了:

  • .env 加载
  • 必填/可选环境变量读取
  • config summary 输出
  • 环境模式判断

不一致

  • home 使用 ENVIRONMENT
  • 其他服务使用 ENV

这会提高部署、调试和文档维护成本。

3. 当前 shared 层只抽了一半

项目已经有这些共享能力:

  • shared/security_headers.py
  • shared/limiter.py
  • shared/health.py
  • shared/templating.py
  • shared/ports.py

说明项目已经具备共享层思路,但还没有把最核心的 app/config 装配流程统一起来。


本次目标

本 PR1 完成后,应该达到:

  1. 五个服务 main.py 只保留:

    • service 专属 lifespan
    • router 列表
    • service 元信息
    • 调用统一 app factory
  2. 五个服务 config.py 改为复用共享 helper

    • .env 加载
    • required/optional env 读取
    • 环境模式判定
    • summary 输出
  3. ENV 成为统一主环境变量,home 暂时兼容 ENVIRONMENT

  4. 不改变外部行为:

    • URL 不变
    • /health 响应结构不变
    • 404/500 页面行为兼容
    • dev 模式 docs_url 保持可用

非目标

这次不做

  • 不重做 service API
  • 不收敛数据库 migration 归属
  • 不彻底消灭所有 sys.path.insert
  • 不迁移目录结构
  • 不改业务权限与业务逻辑

这些属于后续 PR。


方案设计

一、共享 app factory

新增:

  • shared/app_factory.py

职责:

  • 创建标准化 FastAPI app
  • 安装安全头
  • 安装 limiter
  • 挂载静态目录
  • 注册 routers
  • 注册默认错误处理
  • 注册 /health

建议接口:

def create_service_app(
    *,
    service_name: str,
    title: str,
    description: str,
    version: str,
    is_development: bool,
    templates_dir,
    routers: list,
    static_dir=None,
    lifespan=None,
):
    ...

兼容要求

  • service_name 用于 health 响应,例如 blog.ephron.ren
  • static_dir=None 时跳过 static 挂载
  • canvas 当前是“目录存在才挂载”,新实现必须兼容

二、共享错误处理安装器

新增:

  • shared/error_handlers.py

职责:

  • 安装 404/500 handler
  • /api/... 返回 JSON
  • 页面路径返回模板
  • production 不暴露原始异常文本
  • development 可注入错误信息方便调试

建议接口:

def install_default_error_handlers(app, *, templates_dir, is_development): ...

兼容要求

  • API 404 继续返回 {"detail": "Not Found"}
  • 页面 404/500 继续使用各服务自己模板目录中的 404.html / 500.html

三、共享 config helper

新增:

  • shared/config_base.py

职责:

  • 加载服务目录下 .env
  • 提供 required/optional env 读取 helper
  • 统一环境模式判定
  • 统一 config summary 输出

建议接口:

from pathlib import Path

def load_service_env(service_root: Path) -> None: ...
def get_required_env(key: str) -> str: ...
def get_optional_env(key: str, default: str) -> str: ...
def resolve_environment(*, legacy_key: str | None = None) -> tuple[str, bool]: ...
def print_config_summary(service_name: str, items: dict[str, str]) -> None: ...

兼容要求

  • resolve_environment(legacy_key="ENVIRONMENT")ENV 缺失时回退旧字段
  • ENVENVIRONMENT 同时存在,以 ENV 为准

具体实施步骤

Step 1先补 shared 层单元测试

新增:

  • tests/test_shared_config_base.py
  • tests/test_error_handlers.py
  • tests/test_app_factory.py

覆盖这些能力:

test_shared_config_base.py

  • 能加载指定服务目录 .env
  • required env 缺失时报清晰错误
  • ENV 优先于 ENVIRONMENT
  • summary 输出格式稳定

test_error_handlers.py

  • /api/... 404 返回 JSON
  • 页面 404 返回模板
  • production 500 不暴露原始异常文本
  • development 500 可显示 error_message

test_app_factory.py

  • 自动安装安全头
  • 自动安装 limiter
  • 自动挂 routers
  • static_dir 存在时挂载 /static
  • static_dir=None 时不挂载
  • 自动提供 /health
  • dev 模式 docs_url 存在prod 模式关闭

Step 2实现 shared/config_base.py

约束:

  • 共享层只处理“如何读配置”,不处理每个服务自己的业务字段
  • 不在这里写数据库或业务逻辑

Step 3实现 shared/error_handlers.py

要求:

  • 内部复用 shared.templating.create_templates
  • 用 path 前缀判断 API/页面行为
  • production 不直接暴露 str(exc)

Step 4实现 shared/app_factory.py

应复用:

  • shared.security_headers.install_security_headers
  • shared.limiter.create_limiter
  • shared.error_handlers.install_default_error_handlers
  • shared.health.build_health_response

不要做的事:

  • 不在 factory 中写数据库初始化
  • 不在 factory 中塞业务判断

factory 只负责通用 app 壳。


Step 5改造五个服务 config.py

涉及:

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

逐服务注意点

Auth

保留:

  • AUTH_SECRET_KEY
  • DATABASE_PATH
  • COOKIE_DOMAIN
  • COOKIE_NAME
  • TOKEN_MAX_AGE
  • TEMPLATES_DIR

要求:

  • validate_config() 不变
  • summary 输出语义基本不变

Blog

保留:

  • CONTENT_DIR 校验
  • CACHE_DIR.mkdir(...)
  • SEARCH_INDEX_DIR.mkdir(...)

Canvas

保留:

  • CONTENT_DIR 不存在时自动创建

不能把当前“自动建目录”改成“配置错误退出”。

Prompt

保留:

  • DATABASE_PATH
  • TOKEN_MAX_AGE
  • 当前 summary 语义

Home

重点:

  • 进入统一 config helper 体系
  • 兼容 ENVIRONMENT
  • AUTH_BASE_URL / AUTH_LOGIN_URL 行为不变

必须验证:

  • 仅设置 ENVIRONMENT=development 时仍能进入开发模式
  • 若同时设置 ENV=productionENVIRONMENT=development,以 ENV 为准

Step 6改造五个服务 main.py

涉及:

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

目标:

  • 删除重复 app 初始化逻辑
  • 改为调用 shared.app_factory.create_service_app(...)

每个服务保留:

  • service 专属 lifespan
  • router 列表
  • service 元信息

特别注意Home 的 /health

home/src/routes/pages.py 当前也定义了 /health

这次应统一只保留一处,建议:

  • 删除 pages.py 中的 /health
  • 统一由 app factory 提供

否则容易出现重复注册或行为漂移。


Step 7调整回归测试

建议检查并更新:

  • tests/test_security_hardening.py
  • tests/test_frontend_backend_reuse_contract.py

新增建议断言:

  • 所有服务继续使用 shared template factory
  • 所有服务通过统一 app factory 提供 health
  • 所有服务继续保留安全头
  • home 环境兼容逻辑同时覆盖 ENVENVIRONMENT

Step 8完整验证

建议执行:

python -m pytest tests -q
python -m pytest auth/tests -q
python -m pytest blog/tests -q
python -m pytest canvas/tests -q
python -m pytest home/tests -q
python -m pytest prompt/tests -q

如本地可启动,再手动验证:

python main.py --reload

检查:

  • 五个服务均能启动
  • /health 正常
  • API 不存在路径返回 JSON 404
  • 页面不存在路径返回各自 404 模板

预计改动文件

新增

  • shared/config_base.py
  • shared/error_handlers.py
  • shared/app_factory.py
  • tests/test_shared_config_base.py
  • tests/test_error_handlers.py
  • tests/test_app_factory.py

修改

  • auth/src/config.py
  • blog/src/config.py
  • canvas/src/config.py
  • home/src/config.py
  • prompt/src/config.py
  • auth/src/main.py
  • blog/src/main.py
  • canvas/src/main.py
  • home/src/main.py
  • prompt/src/main.py
  • home/src/routes/pages.py(删除重复 /health
  • tests/test_security_hardening.py
  • tests/test_frontend_backend_reuse_contract.py

风险与注意事项

1. Home 环境变量兼容风险

历史部署如果只写了 ENVIRONMENT,不能因为统一而直接失效。

2. 404/500 行为变化风险

抽象错误处理时,很容易把 API 路径判断或模板上下文改坏,必须用单元测试兜住。

3. Canvas static 行为变化风险

当前是“存在才挂载”,不能在 app factory 中粗暴统一成“总是挂载”。

4. 测试依赖旧实现细节

如果某些测试直接断言 main.py 中出现某段文本,改造后会失效,需要调整为验证行为而不是验证源码写法。


建议提交拆分

建议拆成 5 个 commit便于实现与 review

  1. test: add shared config and app factory coverage
  2. feat: add shared config base
  3. feat: add shared error handlers and app factory
  4. refactor: migrate service config modules to shared helpers
  5. refactor: migrate service main modules to shared app factory

完成定义

本 PR1 完成后,必须满足:

  • 五个服务 main.py 不再手写重复 app 装配
  • 五个服务 config.py 使用共享 helper
  • ENV 成为统一主环境变量home 兼容 ENVIRONMENT
  • /health 外部行为不变
  • 404/500 行为兼容
  • 全部现有测试通过,新增 shared 测试通过
  • 没有新增 sys.path.insert 使用点

后续建议

PR1 合并后,建议继续:

  1. PR2shared/service_api/*tests/helpers/*
  2. PR3逐步收敛 import 结构,减少 src 冲突与 sys.path.insert
  3. PR4统一 DB schema / migration ownership

这样可以保证每一步都是建立在更稳定的基础上,而不是继续在重复结构上迭代。