目标项目设计
最终项目叫 durable-mini。它是一个教学版 Durable Execution runtime,用订单处理作为业务场景。
技术栈:
| 层 | 技术 |
|---|---|
| 教程站点 | VitePress |
| 后端运行时 | Python + FastAPI |
| 持久化 | MySQL |
| 前端控制台 | React + Vite |
功能目标
用户可以在前端创建一个订单 workflow,然后观察:
- invocation 被创建。
- worker 执行多个 durable steps。
- 故障注入后,worker 自动重试。
- 已完成 step 在重放时不会再次执行。
- 业务订单表
business_orders随 durable steps 更新状态。 - timer 到期后 workflow 继续。
业务流程
业务代码看起来仍然像普通函数:
python
def process_order(ctx, payload):
order = ctx.run("create-order", lambda: create_order(ctx, payload))
payment = ctx.run("charge-payment", lambda: charge_payment(ctx, order))
inventory = ctx.run("reserve-inventory", lambda: reserve_inventory(ctx, order))
ctx.run("mark-settlement-waiting", lambda: mark_settlement_waiting(ctx, order))
ctx.sleep("settlement-wait", seconds=5)
receipt = ctx.run("send-receipt", lambda: send_receipt(ctx, order, payment))
return {"order": order, "payment": payment, "inventory": inventory, "receipt": receipt}区别在于 ctx.run 和 ctx.sleep 不只是函数调用,它们会读写 Journal。
运行时状态和业务状态
这个项目故意保留两套状态:
| 状态来源 | 表 | 负责回答 |
|---|---|---|
| 运行时状态 | invocations | workflow 是否运行、暂停、重试、完成 |
| 执行历史 | journal_entries | 哪个 durable step 已经提交,重放时是否跳过 |
| 业务状态 | business_orders | 订单当前对用户和运营系统意味着什么 |
这三张表不能混在一起。WAITING_TIMER 是运行时状态,表示 workflow 暂停;WAITING_SETTLEMENT 是业务状态,表示订单已经扣款和预留库存,正在等待结算窗口。
系统结构
为了让学习路径清晰,教学版把运行时和业务 handler 放在同一个 FastAPI 进程。生产系统会把服务协议、worker、日志服务器、元数据服务拆开。
目录结构
text
examples/durable-mini/
├── docker-compose.yml
├── README.md
├── backend/
│ ├── requirements.txt
│ └── app/
│ ├── main.py
│ ├── db.py
│ ├── models.py
│ ├── schemas.py
│ ├── engine.py
│ └── handlers.py
└── frontend/
├── package.json
├── index.html
└── src/
├── main.tsx
├── App.tsx
└── styles.css教学版明确不做什么
| 不实现 | 原因 |
|---|---|
| 多节点复制日志 | 需要复杂的 quorum、segment、failover,超出入门项目 |
| SDK 服务协议 | 先聚焦 Journal 语义,避免陷入网络协议细节 |
| 分区和 sharding | 单机教学更容易观察状态 |
| exactly-once 跨服务通信 | 需要 outbox、dedupe、destination partition 等机制 |
| workflow 版本迁移 | 会在扩展方向解释 |