Files
home-automation/docs/python-rewrite-plan.md
T

315 lines
8.7 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.
# 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 1Python 骨架与通用基础设施
- 建立 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,但会影响第一阶段“兼容到什么程度”的具体定义。