# Supabase 统一服务生命周期设计(优化版) **Date:** 2026-03-06 **Status:** Draft --- ## 0. Intake Contract - Objective: 将 Supabase 客户端纳入统一服务生命周期管理,避免每次请求重复创建客户端。 - Deliverable: 新增 `SupabaseService`,并基于 `service_interface.py` 的 `ServiceRegistry` 提供统一初始化/关闭路径,完成 auth 侧迁移。 - Constraints: - 保持现有 `core.config.settings` 的配置读取行为不变。 - 不引入 `os.environ` 直接读取。 - 不改变现有 API 语义。 - Verification target: - 通过单元测试证明 Supabase 服务初始化、关闭、健康检查行为。 - 通过应用启动测试证明统一初始化流程可用。 - 通过 auth 相关测试证明迁移后业务行为一致。 --- ## 1. 复杂度与风险分级 - Complexity: `S2` - 原因:涉及多文件改造(`services/base`、`app.py`、`v1/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 目标文件结构 ```text 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` 只需声明: ```python 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`)。 注册方式: ```python 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_secret`(JWT 校验现有逻辑继续使用) ### 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` 手写初始化。