Skip to content

入口与运行时装配

后端装配边界回答的是:应用是如何被创建的,运行时依赖是如何进入系统的,FastAPI dependency 又是如何从 app.state 中取回这些依赖的。

关键文件

文件当前职责
app/main.py暴露 app = create_app(),保留少量兼容导出
app/bootstrap.pybackend composition root
app/runtime.pyapp.state 绑定与 resolver
app/api/runtime_dependencies.pyruntime 依赖的 FastAPI adapter

装配链路

入口导出main.py
应用装配create_app()
基础设施实例settings / db / storage / rag
运行时绑定bind_runtime_context
应用状态app.state
接口依赖适配runtime_dependencies.py
业务接口app/api/*

main.py

当前职责非常克制:

  • bootstrap.py 导入 create_app
  • bootstrap.py 透传 build_cors_config
  • 实例化 app = create_app()

这说明 main.py 的角色主要是:

  • 暴露应用入口
  • 给现有测试提供兼容导出

bootstrap.py

create_app() 当前完成的装配动作包括:

  1. 获取 Settings
  2. 创建 FastAPI app
  3. 构建 DB engine 与 session factory
  4. 创建 AvatarStorageService
  5. 通过 bind_runtime_context() 把 runtime 对象挂到 app.state
  6. 配置 static delivery
  7. 配置 CORS
  8. 注册 router
  9. 注册 //health

router 注册事实

当前 ROUTER_REGISTRATIONS 中包含:

  • auth
  • user
  • safety
  • chat
  • prescription
  • sport
  • companion
  • assessment

其中 companion debug router 只在 DEVELOPMENT_MODE 下额外注册。

runtime.py

绑定函数

bind_runtime_context() 负责把以下对象挂到 app.state

  • settings
  • session_factory
  • db_engine
  • rag_engine_getter
  • avatar_storage_service

resolver

runtime.py 同时提供:

  • resolve_runtime_settings()
  • resolve_session_factory()
  • resolve_db_engine()
  • resolve_rag_engine()
  • resolve_avatar_storage_service()

这些 resolver 的策略是:

  • 优先读 app.state
  • 缺失时回退到默认全局对象

runtime_dependencies.py

这是 runtime 边界对 FastAPI adapter 的薄封装。

当前主要导出:

  • get_sms_service()
  • get_avatar_storage_service()

它们通过 Request.app 拿到 app,再调用 runtime resolver,避免 API router 直接拼装依赖。

为什么这个边界重要

  • 如果 runtime 依赖重新散落到 router/service 内部,系统会很快回到“看起来能跑,但难以替换、难以测试、难以交接”的状态。
  • 当前这套装配方式让 settings、session、db、rag、storage 至少都有一个明确读取入口。
  • 后续新增基础设施依赖时,优先考虑是否应进入 bootstrap/runtime,而不是在业务代码里随手 new。

新增 runtime 依赖准入

判断问题推荐处理
依赖是否跨多个 router / service 使用是,则优先进入 bootstrap/runtime 统一装配
依赖是否需要测试替换是,则提供 resolver 或 FastAPI dependency
依赖是否持有连接池、client、存储实例是,避免在 route 内反复创建
依赖是否只服务单个局部纯函数否,不需要进入 runtime,可留在局部 helper
依赖是否包含密钥、连接串或外部 provider client需要从 settings 读取,并确保日志不输出敏感值

判断 runtime 边界时,重点不是“能不能 import 到”,而是依赖生命周期、替换方式和测试入口是否清楚。

来源锚点

  • apps/backend_service/app/main.py
  • apps/backend_service/app/bootstrap.py
  • apps/backend_service/app/runtime.py
  • apps/backend_service/app/api/runtime_dependencies.py