Files
social-app/docs/plans/2026-03-06-supabase-service-design.md
T

8.4 KiB
Raw Blame History

Supabase 统一服务生命周期设计(优化版)

Date: 2026-03-06
Status: Draft


0. Intake Contract

  • Objective: 将 Supabase 客户端纳入统一服务生命周期管理,避免每次请求重复创建客户端。
  • Deliverable: 新增 SupabaseService,并基于 service_interface.pyServiceRegistry 提供统一初始化/关闭路径,完成 auth 侧迁移。
  • Constraints:
    • 保持现有 core.config.settings 的配置读取行为不变。
    • 不引入 os.environ 直接读取。
    • 不改变现有 API 语义。
  • Verification target:
    • 通过单元测试证明 Supabase 服务初始化、关闭、健康检查行为。
    • 通过应用启动测试证明统一初始化流程可用。
    • 通过 auth 相关测试证明迁移后业务行为一致。

1. 复杂度与风险分级

  • Complexity: S2
    • 原因:涉及多文件改造(services/baseapp.pyv1/auth、测试)。
  • Risk Tier: L1
    • 原因:涉及应用启动链路和认证网关依赖,但不改变对外接口契约。

L1 Gate 要求:执行 refactor-cleaner 审视冗余与结构风险(code-reviewer 可选)。


2. 现状与问题

2.1 当前现状

  • SupabaseAuthGateway__init__ 内直接 create_client(...),每次实例化都会创建 anon/admin 客户端。
  • get_auth_service() 当前每次请求都会 new SupabaseAuthGateway(),导致客户端重复构造。
  • ServiceRegistry 已存在,但目前主要用于注册,应用启动仍是手写逐个初始化。

2.2 核心问题

  1. 生命周期不统一:Supabase 没有接入应用启动/关闭的统一管理。
  2. 初始化代码重复趋势:服务增多后,app.py 的 lifespan 会继续膨胀。
  3. 网关构造时机风险:若在应用未初始化阶段取客户端,可能抛运行时异常。

3. 优化设计(推荐方案)

3.1 方案摘要

service_interface.py 基础上新增统一生命周期函数,按服务名列表批量初始化/关闭;app.py 仅声明服务顺序,减少样板代码。Supabase 使用 config.supabase 作为默认配置来源,保持 settings 行为一致。

3.2 目标文件结构

backend/src/services/base/
├── __init__.py
├── service_interface.py          # 扩展:统一生命周期函数
├── redis.py
└── supabase.py                   # 新增

3.3 service_interface 统一初始化能力(新增)

service_interface.py 新增以下函数(建议命名):

  • resolve_registered_services(service_names: list[str]) -> list[BaseServiceProvider]
  • initialize_registered_services(service_names: list[str]) -> tuple[bool, list[BaseServiceProvider]]
  • close_registered_services(services: list[BaseServiceProvider]) -> bool

约束与行为:

  1. 初始化按 service_names 顺序执行。
  2. 任一服务初始化失败时:
    • 返回 False
    • 对已成功初始化的服务按逆序执行关闭回滚。
  3. 关闭按逆序执行,最大化依赖安全性。
  4. 日志必须包含失败服务名和错误摘要。

这样 app.py 只需声明:

SERVICE_STARTUP_ORDER = ["redis", "supabase"]

并调用统一函数,减少重复初始化样板。

3.4 SupabaseService 设计

supabase.py 关键点:

  • 继承 BaseServiceProvider
  • 构造函数签名:
    • def __init__(self, settings: SupabaseSettings | None = None) -> None
    • 默认 settings or config.supabase,确保与当前配置源一致。
  • initialize():创建 anon/admin 两个 client,失败返回 False
  • close()
    • 清空 _client_admin_client
    • self._set_initialized(False)
  • health_check()
    • 必须进行至少一个轻量真实请求验证,不仅检查本地对象存在。
    • 返回结构与 RedisService.health_check()风格一致(status + details)。

注册方式:

supabase_service: SupabaseService = register_service_instance(
    "supabase", SupabaseService()
)

3.5 app.py 改造

当前手写 redis_service.initialize() 改为调用统一初始化函数。

目标行为:

  1. 启动阶段:
    • 调用 initialize_registered_services(["redis", "supabase"])
    • 失败则 raise RuntimeError("Service initialization failed")
  2. 关闭阶段:
    • 调用 close_registered_services(initialized_services)

3.6 AuthGateway 迁移策略(避免构造时机问题)

不建议在 SupabaseAuthGateway.__init__ 里立即绑定 client;改为按需获取:

  • 保留网关对象轻量化。
  • 在每个业务方法内部通过 supabase_service.get_client() / get_admin_client() 取实例。

优点:

  1. 避免模块导入或依赖构建阶段误触未初始化 client。
  2. users/dependencies.py 中全局缓存 gateway 的场景更安全。
  3. 不改变业务层接口。

4. 配置与兼容性保证

4.1 settings/config 行为不变

迁移后依然通过 core.config.settings.config.supabase 读取:

  • url
  • anon_key
  • service_role_key
  • jwt_secretJWT 校验现有逻辑继续使用)

4.2 环境变量兼容

由于 Settings + env_nested_delimiter 机制不变,现有环境变量命名与 .env 内容无需修改。

4.3 对现有代码影响

  • API 层 schema/路由不变。
  • 认证行为不变。
  • 仅优化客户端生命周期与启动流程。

5. 实施计划(可执行)

Task 1: 扩展统一生命周期接口

Files

  • Modify: backend/src/services/base/service_interface.py
  • Test: backend/tests/unit/services/base/test_service_interface.py(新增)

Steps

  1. 写失败测试:初始化顺序、失败回滚、关闭逆序。
  2. 实现生命周期函数。
  3. 跑单测确认通过。

Task 2: 新增 SupabaseService

Files

  • Create: backend/src/services/base/supabase.py
  • Modify: backend/src/services/base/__init__.py
  • Test: backend/tests/unit/services/base/test_supabase.py

Steps

  1. 写失败测试(init success/fail、close、health_check)。
  2. 实现 SupabaseService 与实例注册。
  3. 跑单测。

Task 3: 接入 app lifespan 统一初始化

Files

  • Modify: backend/src/app.py
  • Test: backend/tests/integration/test_app_lifespan.py(新增或扩展)

Steps

  1. 写失败测试(supabase init fail 时应用启动失败)。
  2. 替换手写初始化为统一函数。
  3. 跑集成测试。

Task 4: 迁移 AuthGateway 获取 client 方式

Files

  • Modify: backend/src/v1/auth/gateway.py
  • Optional Modify: backend/src/v1/auth/dependencies.py
  • Optional Modify: backend/src/v1/users/dependencies.py
  • Test: backend/tests/unit/v1/auth/test_gateway.py(扩展)

Steps

  1. 写失败测试(未初始化时错误、初始化后正常调用)。
  2. 改为方法内按需取 client。
  3. 跑 auth 相关单测。

Task 5: 全量验证与门禁

Commands

  • uv run ruff check backend/src backend/tests
  • uv run basedpyright
  • uv run pytest backend/tests/unit/services/base -q
  • uv run pytest backend/tests/unit/v1/auth -q
  • uv run pytest backend/tests/integration -q

输出要求:记录每条命令 pass/fail 与关键摘要。


6. 验收标准(更新)

  • SupabaseService 继承 BaseServiceProvider 并注册到 ServiceRegistry
  • service_interface.py 提供统一初始化/关闭函数
  • app.py 通过统一函数初始化 redis + supabase
  • Supabase 配置读取仍仅来自 core.config.settings.config
  • auth/gateway.py 不再在 __init__ 新建客户端
  • 初始化失败具备回滚关闭逻辑
  • 单元/集成测试覆盖核心迁移路径并通过

7. 风险与缓解

风险 级别 缓解
统一初始化函数引入顺序错误 显式 SERVICE_STARTUP_ORDER + 顺序测试
Supabase 健康检查误报 使用真实轻量请求,不只做对象检查
gateway 与生命周期耦合导致运行时错误 改为方法内按需取 client,并覆盖未初始化测试
迁移影响现有 auth 行为 保持 service 接口不变,补充回归测试

8. 完成定义(Completion Contract

  1. Complexity: S2
  2. Risk Tier: L1
  3. Gates:
    • 必需:refactor-cleaner
    • 可选:code-reviewer(建议在合并前执行)
  4. Verification evidence:
    • 提供 lint/typecheck/unit/integration 命令结果
  5. Remaining risks/follow-ups:
    • 若后续新增第三方服务,沿用 ServiceRegistry + 统一生命周期函数 接入,不再在 app.py 手写初始化。