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

261 lines
8.4 KiB
Markdown
Raw Normal View History

# 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` 手写初始化。