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

8.7 KiB
Raw Blame History

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
  • 补上 poolocation 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,但会影响第一阶段“兼容到什么程度”的具体定义。