261 lines
8.4 KiB
Markdown
261 lines
8.4 KiB
Markdown
|
|
# 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` 手写初始化。
|