Add new application structure: - app/main.py - application entry point - app/plugins/ - plugin system with auth plugin: - api/ - REST API endpoints and schemas - authorization/ - auth policies, providers, hooks - domain/ - business logic (service, models, jwt, password) - injection/ - route injection and guards - ops/ - operational utilities - runtime/ - runtime configuration - security/ - middleware, CSRF, dependencies - storage/ - user repositories and models - app/static/ - static assets (scalar.js for API docs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
app.plugins 设计说明
本文基于当前代码实现,说明 backend/app/plugins 的定位、插件设计契约、依赖边界,以及当前 auth 插件是如何在尽量少侵入宿主应用的前提下提供服务的。
1. 总体定位
app.plugins 是应用侧插件边界。它的目标不是做一个通用插件市场,而是在 app 这一层给可拆分的业务能力预留清晰边界,使某一类能力可以:
- 在插件内部自带领域模型、运行时状态和适配器
- 只通过有限的接缝与宿主应用交互
- 在未来保持“可替换、可裁剪、可扩展”
当前目录下实际落地的插件是 auth。
从当前实现看,app.plugins 的方向不是“所有逻辑都塞进 app”,而是:
- 宿主应用负责统一启动、共享基础设施和总路由装配
- 插件负责自己的业务契约、持久化定义、运行时状态和外部适配器
2. 插件设计契约
2.1 插件内部要自带完整能力
当前代码体现出的首要契约是:
插件自己的 ORM、runtime、domain、adapter,原则上都应由插件内部实现,不要把核心业务依赖散落到外部模块。
以 auth 插件为例,它内部已经自带了完整分层:
domain- 配置、错误、JWT、密码、领域模型、服务
storage- 插件自己的 ORM 模型、仓储契约和仓储实现
runtime- 插件自己的运行时配置状态
api- 插件自己的 HTTP router 和 schema
security- 插件自己的 middleware、dependency、csrf、LangGraph 适配
authorization- 插件自己的权限模型、policy 解析和 hook
injection- 插件自己的路由策略注册、注入和校验逻辑
换句话说,插件不是一组零散 helper,而应该是一个自闭合的功能模块。
2.2 宿主应用只提供共享基础设施,不承接插件内部逻辑
当前约束不是“插件完全独立进程”,而是:
- 插件可以复用应用共享的
engine、session_factory、FastAPI app、路由树 - 但插件自己的表结构、仓储、运行时配置、鉴权逻辑,仍然应由插件自己拥有
这一点在 auth/plugin.toml 里写得很明确:
storage.mode = "shared_infrastructure"- 说明插件拥有自己的 storage definitions 和 repositories
- 但复用应用共享的 persistence infrastructure
所以这里的契约不是“禁止复用基础设施”,而是“不要把插件内部业务实现外包给 app 其他模块”。
2.3 依赖方向要单向
按当前实现,比较理想的依赖方向是:
gateway / app bootstrap
-> plugin public adapters
-> plugin domain / storage / runtime
而不是:
plugin domain
-> 依赖 app 里的业务模块
插件可以使用:
- 共享持久化基础设施
- 宿主应用提供的
app.state - FastAPI / Starlette 等通用框架能力
但不应该把自己的核心业务规则建立在别的业务模块之上,否则后续无法热插拔。
3. 当前 auth 插件的实际结构
当前 auth 插件可以概括为一套“自带模型、自带服务、自带适配器”的认证授权包。
3.1 domain
auth/domain 负责:
config.py- 认证相关配置定义与加载
errors.py- 错误码和错误响应契约
jwt.py- token 编解码
password.py- 密码哈希和校验
models.py- auth 域模型
service.pyAuthService,作为核心业务服务
AuthService 本身只依赖插件内部的 DbUserRepository 和共享 session factory,没有把认证逻辑散到 gateway。
3.2 storage
auth/storage 明确体现了“ORM 由插件自己内部实现”的契约:
models.py- 定义插件自己的
users表模型
- 定义插件自己的
contracts.py- 定义
User、UserCreate和UserRepositoryProtocol
- 定义
repositories.py- 实现
DbUserRepository
- 实现
这里的关键点是:
- 插件自己定义 ORM model
- 插件自己定义 repository protocol
- 插件自己实现 repository
- 外部只需要给它 session / session_factory
这就是插件边界应该保持的最小共享面。
3.3 runtime
auth/runtime/config_state.py 维护插件自己的 runtime config state:
get_auth_config()set_auth_config()reset_auth_config()
这说明运行时配置状态也属于插件内部,而不是由外部模块代持。后续如果别的插件需要自己的缓存、状态机、feature flag,也应沿这个模式内聚在插件内部。
3.4 adapters
auth 插件对外暴露能力主要通过四类 adapter:
api/router.py- HTTP 接口
security/*- middleware、dependency、request user 解析、actor context bridge
authorization/*- capability、policy evaluator、auth hooks
injection/*- route policy registry、guard 注入、启动校验
这类 adapter 的共同特征是:
- 入口能力在插件内定义
- 宿主应用只负责调用和装配
4. 插件如何与宿主应用交互
4.1 总路由只 include,不重写插件逻辑
- 引入
app.plugins.auth.api.router include_router(auth_router)
这说明宿主应用对 auth HTTP 能力的接入是装配式的,而不是在 gateway 里重写一套登录/注册逻辑。
4.2 registrar 负责启动装配,不负责接管插件实现
app/gateway/registrar.py 里,宿主应用做的事情主要是:
app.state.authz_hooks = build_authz_hooks()- 加载并校验 route policy registry
install_route_guards(app)app.add_middleware(CSRFMiddleware)app.add_middleware(AuthMiddleware)
也就是说,宿主应用只负责把插件接进来:
- 注册 middleware
- 安装 route guard
- 把 hooks 和 registry 放到
app.state
真正的鉴权逻辑、认证逻辑、路由策略语义仍然在插件内部。
4.3 共享会话工厂,但业务仓储仍归插件
在 auth/security/dependencies.py 中:
- 插件从
request.app.state.persistence.session_factory取得共享 session factory - 然后自己构造
DbUserRepository - 再自己构造
AuthService
这就是一个很典型的低侵入接缝:
- 外部只提供共享基础设施句柄
- 插件自己决定如何实例化内部依赖
5. 热插拔与低侵入原则
5.1 如果要向其他模块提供服务,应尽量减少入侵
插件给其他模块提供服务时,优先选下面这些方式:
- 暴露 router
- 暴露 middleware / dependency
- 暴露 hook 或 protocol
- 通过
app.state注入少量共享对象 - 使用配置驱动的 route policy / capability,而不是把判断逻辑硬编码进业务路由
不推荐的方式是:
- 在
gateway大量写插件特定分支 - 让别的业务模块直接 import 插件内部 ORM 细节后自行拼逻辑
- 把插件状态散落到全局多个模块中共同维护
5.2 当前 auth 插件已经体现出的低侵入点
当前 auth 插件的低侵入接入点主要有四个:
- 路由接入
gateway.router只include_router
- 中间件接入
registrar只注册AuthMiddleware/CSRFMiddleware
- 策略注入
install_route_guards(app)给路由统一追加Depends(enforce_route_policy)
- hook 接缝
authz_hooks通过app.state暴露,策略构建和权限提供器可以替换
这套结构的好处是:
- 宿主应用改动面集中在装配层
- 插件核心实现集中在插件目录内部
- 替换实现时,不需要在业务路由里逐个修改
5.3 route policy 是低侵入的关键机制
auth/injection/registry_loader.py、validation.py 和 route_injector.py 共同形成了一套很关键的契约:
- 路由策略写在插件自己的
route_policies.yaml - 启动时会校验策略表和真实路由是否一致
- guard 通过统一注入附着到路由,而不是每个 endpoint 手写一遍
这使得插件能够:
- 用配置描述“哪些路由公开、需要哪些 capability、需要哪些 owner policy”
- 避免对宿主路由层做大规模侵入
- 在未来更容易替换或裁剪某个插件
6. 关于“ORM、runtime 都由自己内部实现”的具体说明
这条契约建议明确理解为以下三点:
- 数据模型归插件
- 插件自己的表、Pydantic contract、repository protocol、repository implementation 都放在插件目录内
- 运行时状态归插件
- 插件自己的配置缓存、上下文桥、插件级 hooks 都在插件内部维护
- 外部只暴露基础设施,不接管插件语义
- 例如共享
session_factory、FastAPI app、app.state
- 例如共享
拿 auth 举例:
users表在插件里定义,不在app.infra定义AuthService在插件里实现,不在gateway实现get_auth_config()在插件里维护,不由别的模块缓存AuthMiddleware、route_guard、AuthzHooks都由插件自己提供
这是后续做插件化时最重要的结构前提。
7. 当前作用范围与非目标
就当前实现而言,app.plugins 的作用范围主要是:
- 为应用侧可拆分能力建立模块边界
- 让插件拥有自己的 domain/storage/runtime/adapter
- 通过装配式接缝接入宿主应用
当前非目标也很明确:
- 还不是一个完整的通用插件发现/安装系统
- 还没有做到运行时动态启停插件
- 也不是把共享基础设施完全复制进每个插件
所以“热插拔”在当前阶段更准确的含义是:
- 插件边界尽量独立
- 接入点尽量集中在装配层
- 替换或移除时,改动尽量局限在
registrar、router include、app.statehooks 这些少数位置
8. 后续演进建议
如果后续要继续把 app.plugins 做成更稳定的插件边界,建议保持这些规则:
- 每个插件目录内部都保持
domain/storage/runtime/adapter分层 - 插件自己的 ORM 与 repository 不要下沉到共享业务目录
- 插件向外提供服务时优先暴露 protocol、hook、router、middleware,而不是要求外部 import 内部实现细节
- 插件与宿主应用的接缝尽量限制在:
router.include_router(...)app.add_middleware(...)app.state.*- 生命周期装配
- 配置驱动优先于散落的硬编码接入
- 启动期校验优先于运行时隐式失败
9. 设计总结
可以把当前 app.plugins 的契约总结为一句话:
插件内部拥有自己的业务实现、ORM 和 runtime;宿主应用只提供共享基础设施和装配接缝;对外服务时尽量通过低侵入、可替换的方式接入,以便后续做到真正的热插拔和边界演进。