2.5 KiB
ADR-0003:自研状态机,不用 Temporal / Airflow
- 日期:2026-07-27
- 状态:已接受(方案 §6.1 把 Temporal 列为演进建议,这里给出不在首版用的理由)
背景
工作流编排有成熟方案:Temporal、Airflow、Prefect、OpenTAP。 自研状态机通常是坏味道。
决定
首版自研:engine/states.py 转移表 + engine/state_machine.py 纯函数
engine/runtime.py异步循环。
理由
产品的差异化恰好落在通用引擎不管的那一层:
-
恢复语义不是重试。 Temporal 的重试模型是"再调一次这个 activity"。 我们需要的是"主机蓝屏了 → 带外触发主板 Reset → 等主机起来 → 验证 DUT 重新枚举 → 从检查点续跑"。这是跨越进程、跨越主机生死的物理恢复, 不是函数重试。硬套通用引擎会变成在 activity 里塞一堆状态判断,比自研更难维护。
-
非幂等步骤默认不重跑。 通用引擎的默认值是"重试是安全的"。 我们这里最贵的一条规则是"刷写步骤挂了不要自动重跑"。 跟框架默认值对着干,不如自己控制。
-
冻结现场是一等状态。 数据完整性失败要立刻停止一切覆盖性动作并保留现场。 通用引擎里这是"失败",我们这里它是一个需要保持很久、等人来看的活状态。
-
可测试性。 纯函数状态机能把"30 秒心跳超时后带外在线则走 L3"这种规则 在毫秒内测完。挂在 Temporal 上要起测试环境。后面几波会疯狂往状态机加分支, 测试成本是决定性因素。
-
依赖成本:Temporal 需要自己的数据库和服务集群。客户是内网私有化部署, 多一个必须运维的组件就多一个交付摩擦。
代价
- 跨节点调度、持久化定时器、版本化工作流这些 Temporal 白送的能力要自己做。 这一波不需要(单工位),Phase 3 多工位时会痛。
- 自研状态机容易随时间腐化成 if 堆。缓解:转移表是纯数据、
decide()是纯函数、 每加一个分支必须配单测,三条规矩写进了docs/state-machine.md §6。
什么时候推翻
多工位跨节点调度成为瓶颈时(Phase 3,预计 20+ 工位)。
届时替换范围仅限 runtime.py——states.py / state_machine.py / recovery.py
是纯逻辑,可以原样搬进 Temporal 的 workflow 定义里。
这也是把它们做成纯函数的第二个理由。