deer-flow/backend/app/plugins/README_zh.md
rayhpeng 0f82f8a3a2 feat(app): add plugin system with auth plugin and static assets
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>
2026-04-22 11:31:42 +08:00

12 KiB
Raw Blame History

app.plugins 设计说明

本文基于当前代码实现,说明 backend/app/plugins 的定位、插件设计契约、依赖边界,以及当前 auth 插件是如何在尽量少侵入宿主应用的前提下提供服务的。

1. 总体定位

app.plugins 是应用侧插件边界。它的目标不是做一个通用插件市场,而是在 app 这一层给可拆分的业务能力预留清晰边界,使某一类能力可以:

  1. 在插件内部自带领域模型、运行时状态和适配器
  2. 只通过有限的接缝与宿主应用交互
  3. 在未来保持“可替换、可裁剪、可扩展”

当前目录下实际落地的插件是 auth

从当前实现看,app.plugins 的方向不是“所有逻辑都塞进 app”而是

  1. 宿主应用负责统一启动、共享基础设施和总路由装配
  2. 插件负责自己的业务契约、持久化定义、运行时状态和外部适配器

2. 插件设计契约

2.1 插件内部要自带完整能力

当前代码体现出的首要契约是:

插件自己的 ORM、runtime、domain、adapter原则上都应由插件内部实现不要把核心业务依赖散落到外部模块。

auth 插件为例,它内部已经自带了完整分层:

  1. domain
    • 配置、错误、JWT、密码、领域模型、服务
  2. storage
    • 插件自己的 ORM 模型、仓储契约和仓储实现
  3. runtime
    • 插件自己的运行时配置状态
  4. api
    • 插件自己的 HTTP router 和 schema
  5. security
    • 插件自己的 middleware、dependency、csrf、LangGraph 适配
  6. authorization
    • 插件自己的权限模型、policy 解析和 hook
  7. injection
    • 插件自己的路由策略注册、注入和校验逻辑

换句话说,插件不是一组零散 helper而应该是一个自闭合的功能模块。

2.2 宿主应用只提供共享基础设施,不承接插件内部逻辑

当前约束不是“插件完全独立进程”,而是:

  1. 插件可以复用应用共享的 enginesession_factory、FastAPI app、路由树
  2. 但插件自己的表结构、仓储、运行时配置、鉴权逻辑,仍然应由插件自己拥有

这一点在 auth/plugin.toml 里写得很明确:

  1. storage.mode = "shared_infrastructure"
  2. 说明插件拥有自己的 storage definitions 和 repositories
  3. 但复用应用共享的 persistence infrastructure

所以这里的契约不是“禁止复用基础设施”,而是“不要把插件内部业务实现外包给 app 其他模块”。

2.3 依赖方向要单向

按当前实现,比较理想的依赖方向是:

gateway / app bootstrap
  -> plugin public adapters
     -> plugin domain / storage / runtime

而不是:

plugin domain
  -> 依赖 app 里的业务模块

插件可以使用:

  1. 共享持久化基础设施
  2. 宿主应用提供的 app.state
  3. FastAPI / Starlette 等通用框架能力

但不应该把自己的核心业务规则建立在别的业务模块之上,否则后续无法热插拔。

3. 当前 auth 插件的实际结构

当前 auth 插件可以概括为一套“自带模型、自带服务、自带适配器”的认证授权包。

3.1 domain

auth/domain 负责:

  1. config.py
    • 认证相关配置定义与加载
  2. errors.py
    • 错误码和错误响应契约
  3. jwt.py
    • token 编解码
  4. password.py
    • 密码哈希和校验
  5. models.py
    • auth 域模型
  6. service.py
    • AuthService,作为核心业务服务

AuthService 本身只依赖插件内部的 DbUserRepository 和共享 session factory没有把认证逻辑散到 gateway

3.2 storage

auth/storage 明确体现了“ORM 由插件自己内部实现”的契约:

  1. models.py
    • 定义插件自己的 users 表模型
  2. contracts.py
    • 定义 UserUserCreateUserRepositoryProtocol
  3. repositories.py
    • 实现 DbUserRepository

这里的关键点是:

  1. 插件自己定义 ORM model
  2. 插件自己定义 repository protocol
  3. 插件自己实现 repository
  4. 外部只需要给它 session / session_factory

这就是插件边界应该保持的最小共享面。

3.3 runtime

auth/runtime/config_state.py 维护插件自己的 runtime config state

  1. get_auth_config()
  2. set_auth_config()
  3. reset_auth_config()

这说明运行时配置状态也属于插件内部而不是由外部模块代持。后续如果别的插件需要自己的缓存、状态机、feature flag也应沿这个模式内聚在插件内部。

3.4 adapters

auth 插件对外暴露能力主要通过四类 adapter

  1. api/router.py
    • HTTP 接口
  2. security/*
    • middleware、dependency、request user 解析、actor context bridge
  3. authorization/*
    • capability、policy evaluator、auth hooks
  4. injection/*
    • route policy registry、guard 注入、启动校验

这类 adapter 的共同特征是:

  1. 入口能力在插件内定义
  2. 宿主应用只负责调用和装配

4. 插件如何与宿主应用交互

4.1 总路由只 include不重写插件逻辑

app/gateway/router.py 只是:

  1. 引入 app.plugins.auth.api.router
  2. include_router(auth_router)

这说明宿主应用对 auth HTTP 能力的接入是装配式的,而不是在 gateway 里重写一套登录/注册逻辑。

4.2 registrar 负责启动装配,不负责接管插件实现

app/gateway/registrar.py 里,宿主应用做的事情主要是:

  1. app.state.authz_hooks = build_authz_hooks()
  2. 加载并校验 route policy registry
  3. install_route_guards(app)
  4. app.add_middleware(CSRFMiddleware)
  5. app.add_middleware(AuthMiddleware)

也就是说,宿主应用只负责把插件接进来:

  1. 注册 middleware
  2. 安装 route guard
  3. 把 hooks 和 registry 放到 app.state

真正的鉴权逻辑、认证逻辑、路由策略语义仍然在插件内部。

4.3 共享会话工厂,但业务仓储仍归插件

auth/security/dependencies.py 中:

  1. 插件从 request.app.state.persistence.session_factory 取得共享 session factory
  2. 然后自己构造 DbUserRepository
  3. 再自己构造 AuthService

这就是一个很典型的低侵入接缝:

  1. 外部只提供共享基础设施句柄
  2. 插件自己决定如何实例化内部依赖

5. 热插拔与低侵入原则

5.1 如果要向其他模块提供服务,应尽量减少入侵

插件给其他模块提供服务时,优先选下面这些方式:

  1. 暴露 router
  2. 暴露 middleware / dependency
  3. 暴露 hook 或 protocol
  4. 通过 app.state 注入少量共享对象
  5. 使用配置驱动的 route policy / capability而不是把判断逻辑硬编码进业务路由

不推荐的方式是:

  1. gateway 大量写插件特定分支
  2. 让别的业务模块直接 import 插件内部 ORM 细节后自行拼逻辑
  3. 把插件状态散落到全局多个模块中共同维护

5.2 当前 auth 插件已经体现出的低侵入点

当前 auth 插件的低侵入接入点主要有四个:

  1. 路由接入
    • gateway.routerinclude_router
  2. 中间件接入
    • registrar 只注册 AuthMiddleware / CSRFMiddleware
  3. 策略注入
    • install_route_guards(app) 给路由统一追加 Depends(enforce_route_policy)
  4. hook 接缝
    • authz_hooks 通过 app.state 暴露,策略构建和权限提供器可以替换

这套结构的好处是:

  1. 宿主应用改动面集中在装配层
  2. 插件核心实现集中在插件目录内部
  3. 替换实现时,不需要在业务路由里逐个修改

5.3 route policy 是低侵入的关键机制

auth/injection/registry_loader.pyvalidation.pyroute_injector.py 共同形成了一套很关键的契约:

  1. 路由策略写在插件自己的 route_policies.yaml
  2. 启动时会校验策略表和真实路由是否一致
  3. guard 通过统一注入附着到路由,而不是每个 endpoint 手写一遍

这使得插件能够:

  1. 用配置描述“哪些路由公开、需要哪些 capability、需要哪些 owner policy”
  2. 避免对宿主路由层做大规模侵入
  3. 在未来更容易替换或裁剪某个插件

6. 关于“ORM、runtime 都由自己内部实现”的具体说明

这条契约建议明确理解为以下三点:

  1. 数据模型归插件
    • 插件自己的表、Pydantic contract、repository protocol、repository implementation 都放在插件目录内
  2. 运行时状态归插件
    • 插件自己的配置缓存、上下文桥、插件级 hooks 都在插件内部维护
  3. 外部只暴露基础设施,不接管插件语义
    • 例如共享 session_factory、FastAPI app、app.state

auth 举例:

  1. users 表在插件里定义,不在 app.infra 定义
  2. AuthService 在插件里实现,不在 gateway 实现
  3. get_auth_config() 在插件里维护,不由别的模块缓存
  4. AuthMiddlewareroute_guardAuthzHooks 都由插件自己提供

这是后续做插件化时最重要的结构前提。

7. 当前作用范围与非目标

就当前实现而言,app.plugins 的作用范围主要是:

  1. 为应用侧可拆分能力建立模块边界
  2. 让插件拥有自己的 domain/storage/runtime/adapter
  3. 通过装配式接缝接入宿主应用

当前非目标也很明确:

  1. 还不是一个完整的通用插件发现/安装系统
  2. 还没有做到运行时动态启停插件
  3. 也不是把共享基础设施完全复制进每个插件

所以“热插拔”在当前阶段更准确的含义是:

  1. 插件边界尽量独立
  2. 接入点尽量集中在装配层
  3. 替换或移除时,改动尽量局限在 registrarrouter includeapp.state hooks 这些少数位置

8. 后续演进建议

如果后续要继续把 app.plugins 做成更稳定的插件边界,建议保持这些规则:

  1. 每个插件目录内部都保持 domain / storage / runtime / adapter 分层
  2. 插件自己的 ORM 与 repository 不要下沉到共享业务目录
  3. 插件向外提供服务时优先暴露 protocol、hook、router、middleware而不是要求外部 import 内部实现细节
  4. 插件与宿主应用的接缝尽量限制在:
    • router.include_router(...)
    • app.add_middleware(...)
    • app.state.*
    • 生命周期装配
  5. 配置驱动优先于散落的硬编码接入
  6. 启动期校验优先于运行时隐式失败

9. 设计总结

可以把当前 app.plugins 的契约总结为一句话:

插件内部拥有自己的业务实现、ORM 和 runtime宿主应用只提供共享基础设施和装配接缝对外服务时尽量通过低侵入、可替换的方式接入以便后续做到真正的热插拔和边界演进。