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

261 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 手写初始化。