BLOGE
0.9.8-RC1在线版 · 事实校验 2026-09-15 · English
第三阶段回顾 —— 组合与时间(第 10–13 章)
你现在可以用可复用的部件组合大型工作流、遍历集合、挂起等待外部事件,并在进程重启后恢复执行。
你学到了什么
第 10 章 —— 用子图复用 引入了 subgraph("name") 语法。你学会了用 DslCompiler.registerSubGraph() 注册预构建的 Graph、将其嵌入父 graph,并控制作用域隔离(isolated vs. parent)。你还看到了 dynamicSubGraph(运行时动态生成 DSL)在什么场景下更合适。
第 11 章 —— 批处理与迭代 展示了两个循环原语:foreach(遍历集合,每个元素执行一次子 graph 并收集结果)和 loop(带 max_iterations、delay 和 until 的可控重复)。你了解了批处理结果如何收集,以及 loopIteration 如何提供从零开始的计数器。
第 12 章——等待世界的回应 增加了挂起能力。通过 await,graph 暂停执行且不持续占用 worker thread,只有外部信号到达或超时后才恢复。你练习了按 key 关联事件,以及根据信号是否到达来分支执行。
第 13 章——持久执行 增加了重启恢复所需的 store 与 identity。满足条件的 worker 可以接受 checkpoint、跳过已完成节点并继续剩余前沿;尚未提交 checkpoint 的外部副作用仍需要幂等或对账。
你现在应该能做到
- 将可复用的流程提取为命名子图,并嵌入多个父 graph。
- 用
foreach处理集合、用loop实现轮询循环,并为每种场景选择正确的原语。 - 用
await挂起 graph、关联外部事件,并优雅地处理超时。 - 将持久化 store 接入
GraphEngine,并演示 挂起 → 崩溃 → 冷恢复 → 继续执行 的完整流程。 - 解释 checkpoint、执行标识和 graph 注册表之间的关 系。
这个阶段的常见错误
| 错误 | 为什么会犯 | 如何纠正 |
|---|---|---|
| 在应该用 isolated 作用域时用了 parent | "子图只需要父 graph 的一个值" | 默认使用 isolated 作用域,通过子图输入绑定显式传递所需值。parent 作用域会泄漏整个 context。 |
无限循环(没有 until 也没有 max_iterations) | 忘记 loop 默认是无界的 | 即使 until 通常会先退出,也始终设置 max_iterations 作为安全网。 |
把 await 当成 Thread.sleep() | 混淆了挂起和阻塞的概念 | await 等待时不持续阻塞 worker thread;持久化 wait、timer 与 correlation 状态仍会消耗系统资源。 |
| 开发时跳过持久化 store | “以后再加持久化” | 用内存 store 实现练习 API 与恢复路径,同时记住它们会随进程退出而丢失状态。 |
| 不测试恢复路径 | 只测试没有任何崩溃的快乐路径 | 至少写一个测试:执行到一半,创建新引擎,从 checkpoint 恢复。 |
新增核心词汇
| 术语 | 含义 |
|---|---|
| 子图(Subgraph) | 一个按名称注册的预构建 Graph,作为单个节点嵌入父 graph。 |
foreach | 遍历集合,为每个元素执行一次循环体并收集结果。 |
loop | 最多重复执行 max_iterations 次,可选 delay 和 until 退出条件。 |
loopIteration | 每个循环体内可用的从零开始的内置计数器。 |
await | 挂起 graph 并释放线程,直到外部信号到达或超时。 |
| Checkpoint | 每个节点完成后对 node 输出和执行状态的持久快照。 |
| 冷恢复(Cold recovery) | 在另一个引擎实例上,通过从持久化存储加载 checkpoint 来恢复 graph 执行。 |
试一试:选择正确的时间模型
一个订单流程要处理 200 个商品、最多轮询库存五次,还可能等待客户确认两天。请分别选择 foreach、loop 或 await,并标出哪些状态必须跨进程重启保存。再解释为什么用 sleep 循环替代 await 会占住错误的资源,并隐藏外部 signal 边界。
下一步
第四阶段回顾 将重心从“持久的一次性执行”转向“更长生命周期的编排模式”:session、状态机,以及嵌套生命周期所有权。从第 14 章 —— 多轮 Session开始。