315 lines
8.7 KiB
Markdown
315 lines
8.7 KiB
Markdown
|
|
# Python 重构方案
|
|||
|
|
|
|||
|
|
本文档基于当前 Go 实现,给出迁移到 Python + FastAPI 的设计输入与建议顺序。本文档只讨论迁移方案,不代表已经开始实现。
|
|||
|
|
|
|||
|
|
## 重构原则
|
|||
|
|
|
|||
|
|
- 以当前 Go 实现为唯一事实来源
|
|||
|
|
- 先保持对外行为兼容,再考虑内部清理和优化
|
|||
|
|
- 明确区分“行为兼容”和“内部实现升级”
|
|||
|
|
- 默认不把 Notion 纳入新的 Python 主版本目标,除非后续重新决策
|
|||
|
|
- 尽量按模块迁移,并在模块之间建立清晰契约
|
|||
|
|
|
|||
|
|
## 目标形态
|
|||
|
|
|
|||
|
|
建议的 Python 目标架构:
|
|||
|
|
|
|||
|
|
- 用 FastAPI 提供 HTTP 路由,并自然生成 OpenAPI
|
|||
|
|
- 业务逻辑按模块拆分到 service layer
|
|||
|
|
- 外部系统对接放到 adapter layer
|
|||
|
|
- SQLite 访问放到 repository layer
|
|||
|
|
- 配置采用显式 settings model
|
|||
|
|
- scheduler 是否内嵌在应用进程内,需要在启动语义明确后再决定
|
|||
|
|
- 仅保留极轻量的服务端页面,用于 OAuth 跳转或简单配置
|
|||
|
|
|
|||
|
|
## 建议的 Python 模块边界
|
|||
|
|
|
|||
|
|
### 应用外壳层
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- 读取 settings
|
|||
|
|
- 依赖注入与对象装配
|
|||
|
|
- 路由注册
|
|||
|
|
- lifespan hooks
|
|||
|
|
- scheduler 启停
|
|||
|
|
- 在 FastAPI 中可对应:
|
|||
|
|
- `main.py` 或 app factory
|
|||
|
|
- settings 类
|
|||
|
|
|
|||
|
|
### Poo 领域模块
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- 校验 poo record 输入
|
|||
|
|
- 持久化 poo record
|
|||
|
|
- 查询 latest poo
|
|||
|
|
- 通过接口触发外部副作用
|
|||
|
|
- 建议把副作用依赖抽象为端口:
|
|||
|
|
- `PooRepository`
|
|||
|
|
- `HomeAssistantPublisher`
|
|||
|
|
- `HomeAssistantWebhookClient`
|
|||
|
|
- 如有需要,可保留临时 `LegacyPooMirror` 作为 Notion 过渡适配器
|
|||
|
|
|
|||
|
|
### Location 领域模块
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- 校验并持久化位置点
|
|||
|
|
- 可保持简洁:
|
|||
|
|
- `LocationRepository`
|
|||
|
|
- `LocationService`
|
|||
|
|
|
|||
|
|
### Home Assistant 命令网关
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- 暴露 `/homeassistant/publish`
|
|||
|
|
- 解析 `target/action/content`
|
|||
|
|
- 将命令分发到内部服务
|
|||
|
|
- 兼容性注意:
|
|||
|
|
- 第一阶段应保留当前 `content` 的处理习惯,包括对字符串 payload 的兼容解析
|
|||
|
|
|
|||
|
|
### TickTick 集成模块
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- OAuth start / callback
|
|||
|
|
- token 存储
|
|||
|
|
- task 查询
|
|||
|
|
- 去重
|
|||
|
|
- task 创建
|
|||
|
|
- 建议:
|
|||
|
|
- 把 token persistence 抽象成独立能力,而不是把“改写配置文件”直接塞进业务逻辑
|
|||
|
|
|
|||
|
|
### Home Assistant 出站适配层
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- 发布 sensor state
|
|||
|
|
- 触发 webhook
|
|||
|
|
- 该层小而独立,适合较早迁移
|
|||
|
|
|
|||
|
|
### Legacy Notion 适配层
|
|||
|
|
|
|||
|
|
- 职责:
|
|||
|
|
- 只在分析或过渡阶段表示当前行为
|
|||
|
|
- 默认建议:
|
|||
|
|
- 不放入 Python 第一版正式目标
|
|||
|
|
- 如 cutover 期间确有需要,可以 feature flag 或独立迁移工具的方式暂存
|
|||
|
|
|
|||
|
|
## 实现前需要冻结的兼容契约
|
|||
|
|
|
|||
|
|
在正式编码前,建议先把以下当前行为写成明确契约:
|
|||
|
|
|
|||
|
|
### API 契约
|
|||
|
|
|
|||
|
|
- `POST /poo/record` 的请求字段与当前“成功时空响应体”的行为
|
|||
|
|
- `POST /location/record` 的请求字段与数值解析行为
|
|||
|
|
- `POST /homeassistant/publish` 的 envelope 格式与支持的 `target/action`
|
|||
|
|
- `GET /ticktick/auth/code` 的成功 / 失败语义
|
|||
|
|
|
|||
|
|
### 副作用契约
|
|||
|
|
|
|||
|
|
- `POST /poo/record` 在什么时机会发布 Home Assistant sensor
|
|||
|
|
- `POST /poo/record` 在什么条件下会触发 Home Assistant webhook
|
|||
|
|
- Home Assistant 消息如何映射为 TickTick task title 与 due date
|
|||
|
|
- Home Assistant sensor payload 的结构
|
|||
|
|
|
|||
|
|
### 持久化契约
|
|||
|
|
|
|||
|
|
- 当前 SQLite 表名与主键
|
|||
|
|
- 当前磁盘上的时间戳格式
|
|||
|
|
- Python 第一阶段是否直接复用现有 DB 文件,还是做显式迁移
|
|||
|
|
|
|||
|
|
## 建议的迁移决策
|
|||
|
|
|
|||
|
|
### 决策 1:第一阶段保持对外 API 形状不变
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 当前 API 面很小
|
|||
|
|
- 保持兼容能显著降低切换风险
|
|||
|
|
- 即使保持兼容,FastAPI 仍然可以生成 OpenAPI 文档
|
|||
|
|
|
|||
|
|
### 决策 2:把内部 self-HTTP 改成直接服务调用
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 当前 Go 代码中的 `localhost` 自调用,本质上是内部编排手段
|
|||
|
|
- 这不是一个必须暴露给外部的契约
|
|||
|
|
- Python 版改为直接函数 / service 调用,可以提升清晰度和可测试性
|
|||
|
|
|
|||
|
|
### 决策 3:先继续使用 SQLite
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 当前系统已经使用 SQLite
|
|||
|
|
- 数据模型规模很小
|
|||
|
|
- PostgreSQL 更适合作为 parity 之后的下一阶段演进
|
|||
|
|
|
|||
|
|
### 决策 4:默认不迁 Notion,但要明确记录影响
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 你已经明确表示 Notion 很可能不继续保留
|
|||
|
|
- 当前 Notion 不是“代码里有但没在用”,而是真正参与运行逻辑
|
|||
|
|
- 所以不能静默删除,而要在方案中写清楚删掉后有什么影响
|
|||
|
|
|
|||
|
|
### 决策 5:把 token / auth persistence 做成显式设计
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 当前 TickTick token 处理虽然可用,但运维上比较脆弱
|
|||
|
|
- Python 重构是一个把这件事规范化的机会
|
|||
|
|
|
|||
|
|
## 建议迁移顺序
|
|||
|
|
|
|||
|
|
### Phase 0:盘点与契约确认
|
|||
|
|
|
|||
|
|
- 完成当前系统 inventory
|
|||
|
|
- 确认哪些当前行为是“必须兼容的契约”,哪些只是历史偶然实现
|
|||
|
|
- 明确把 Notion 标为 non-migration scope,除非后续重新决定
|
|||
|
|
|
|||
|
|
### Phase 1:Python 骨架与通用基础设施
|
|||
|
|
|
|||
|
|
- 建立 FastAPI app shell
|
|||
|
|
- 定义 settings / config model
|
|||
|
|
- 定义日志方案
|
|||
|
|
- 定义 SQLite 访问方式
|
|||
|
|
- 定义测试框架与 fixture 策略
|
|||
|
|
- 定义 OpenAPI 生成与导出方式
|
|||
|
|
|
|||
|
|
这一阶段不需要大量迁移业务逻辑,只要搭好后续模块可持续迁入的基础即可。
|
|||
|
|
|
|||
|
|
### Phase 2:先迁最独立、最稳定的业务模块
|
|||
|
|
|
|||
|
|
推荐优先迁移:`location recorder`
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 独立 SQLite 表
|
|||
|
|
- 没有复杂外部副作用
|
|||
|
|
- 没有 OAuth
|
|||
|
|
- 没有 scheduler
|
|||
|
|
|
|||
|
|
这一阶段的交付物可以包括:
|
|||
|
|
|
|||
|
|
- `POST /location/record`
|
|||
|
|
- 与现有 SQLite 兼容的写入逻辑
|
|||
|
|
- 校验与 repository 的单元测试
|
|||
|
|
- 基于临时 SQLite 的 integration test
|
|||
|
|
|
|||
|
|
### Phase 3:迁移 Home Assistant 出站适配层
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 功能面小
|
|||
|
|
- 能为后面的 poo 迁移做铺垫
|
|||
|
|
|
|||
|
|
这一阶段的交付物可以包括:
|
|||
|
|
|
|||
|
|
- sensor publish client
|
|||
|
|
- webhook trigger client
|
|||
|
|
- 针对请求格式与错误处理的 mock tests
|
|||
|
|
|
|||
|
|
### Phase 4:迁移 TickTick adapter
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 相对自洽
|
|||
|
|
- 在完成 Home Assistant 命令网关前就需要它
|
|||
|
|
|
|||
|
|
这一阶段的交付物可以包括:
|
|||
|
|
|
|||
|
|
- OAuth callback endpoint
|
|||
|
|
- token persistence abstraction
|
|||
|
|
- task 创建与去重行为
|
|||
|
|
- 基于 mock HTTP 的集成式测试
|
|||
|
|
|
|||
|
|
### Phase 5:迁移 Home Assistant 命令网关
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 这是外部 automations 的核心编排入口
|
|||
|
|
- 在 location 与 TickTick adapter 准备好后,网关迁移会顺很多
|
|||
|
|
|
|||
|
|
这一阶段的交付物可以包括:
|
|||
|
|
|
|||
|
|
- `/homeassistant/publish`
|
|||
|
|
- 兼容当前 `target/action` 的分发逻辑
|
|||
|
|
- 用进程内 service 调用替代 self-HTTP
|
|||
|
|
- 把现有 Go 测试场景迁成 Python contract tests
|
|||
|
|
|
|||
|
|
### Phase 6:迁移 poo recorder 核心,但默认不带 Notion
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
|
|||
|
|
- 这是最复杂的模块
|
|||
|
|
- 它既有本地 DB,又有 Home Assistant 副作用,当前还耦合 Notion
|
|||
|
|
|
|||
|
|
建议拆成两个子阶段:
|
|||
|
|
|
|||
|
|
- phase 6a:
|
|||
|
|
- 本地 poo DB
|
|||
|
|
- latest poo 查询
|
|||
|
|
- sensor publish
|
|||
|
|
- 可选 webhook trigger
|
|||
|
|
- `/poo/record`
|
|||
|
|
- `/poo/latest`
|
|||
|
|
|
|||
|
|
- phase 6b:
|
|||
|
|
- 如果 cutover 期间必须保留旧逻辑,再做一个临时 legacy Notion 兼容层
|
|||
|
|
|
|||
|
|
### Phase 7:运维加固与切换验证
|
|||
|
|
|
|||
|
|
- 做 Go / Python 路由级契约比对
|
|||
|
|
- 用现有 SQLite 文件或其副本做兼容验证
|
|||
|
|
- 在 staging 环境手动验证 TickTick OAuth
|
|||
|
|
- 用真实 Home Assistant automation payload 做验证
|
|||
|
|
- 导出 OpenAPI YAML
|
|||
|
|
- 再补容器化与部署方案
|
|||
|
|
|
|||
|
|
## 哪些模块适合先迁,哪些适合后迁
|
|||
|
|
|
|||
|
|
### 适合优先迁移
|
|||
|
|
|
|||
|
|
- location recorder
|
|||
|
|
- Home Assistant 出站 client
|
|||
|
|
- TickTick adapter
|
|||
|
|
|
|||
|
|
### 更适合后迁
|
|||
|
|
|
|||
|
|
- Home Assistant 命令网关
|
|||
|
|
- poo recorder 核心
|
|||
|
|
|
|||
|
|
### 现状存在,但建议不迁
|
|||
|
|
|
|||
|
|
- Notion sync adapter
|
|||
|
|
- `poo_recorder_helper` 的 Notion 表反转 CLI
|
|||
|
|
- `location_recorder` helper CLI 脚手架
|
|||
|
|
|
|||
|
|
## 建议的验证策略
|
|||
|
|
|
|||
|
|
### Contract tests
|
|||
|
|
|
|||
|
|
- 基于当前 Go 行为建立 request / response fixtures
|
|||
|
|
- 先把现有 `homeassistant` 测试案例迁成 Python
|
|||
|
|
- 补上 `poo` 与 `location` API 的契约测试
|
|||
|
|
|
|||
|
|
### Integration tests
|
|||
|
|
|
|||
|
|
- 每个模块使用临时 SQLite DB
|
|||
|
|
- Home Assistant 与 TickTick 出站流量通过 mock HTTP 替代
|
|||
|
|
- 若仍保留 scheduler,则为其补定时行为测试
|
|||
|
|
|
|||
|
|
### 手工 staging 验证
|
|||
|
|
|
|||
|
|
- TickTick OAuth callback
|
|||
|
|
- Home Assistant sensor 更新
|
|||
|
|
- Home Assistant webhook 触发
|
|||
|
|
- 当前真实自动化 payload 样例
|
|||
|
|
|
|||
|
|
## 开始实现前仍需明确的问题
|
|||
|
|
|
|||
|
|
- Python 第一阶段是否还要保留“缺少 `notion.token` 就启动失败”的行为,还是直接把 Notion 变成可关闭能力?
|
|||
|
|
- `POST /location/record` 是否要继续保留“非法数字静默变成 0”的兼容行为?
|
|||
|
|
- TickTick token 在第一阶段是否继续写回 YAML,还是立即切到独立 token store?
|
|||
|
|
- 当前 Home Assistant automations 是否真实依赖 `content` 中的单引号 pseudo-JSON?
|
|||
|
|
|
|||
|
|
这些问题不影响当前 inventory,但会影响第一阶段“兼容到什么程度”的具体定义。
|