核心概念
本页解释 Pulse 的数据模型和执行规则。建议先运行快速开始,再用这里的概念理解分支、汇聚、复用与取消。
定义与执行
Pulse 使用四个核心对象:
| 对象 | 保存什么 | 生命周期 |
|---|---|---|
Key[T] | 数据名称与 Go 类型 | 随定义使用 |
Node | ID、描述、读写声明、函数与切面 | 属于图定义 |
Graph | 节点、端口、依赖与挂载的子图 | 可以被反复执行 |
Execution | 本轮输入、数据槽位、状态、错误与取消域 | 对应一次运行 |
可以把 Graph 理解为一份处理说明,把 Execution 理解为按这份说明执行的一个请求。两个请求使用同一份说明,并不共享执行槽位。
每次 g.Run(ctx, in) 都创建新的执行。要取得句柄,使用 g.NewExecution(ctx, in),随后调用 e.Start() 与 e.Wait()。同一个 Execution 只能成功启动一次。
图定义的隔离不意味着业务对象被复制:闭包捕获的计数器、客户端,以及传入的 map、slice、指针仍可能共享。并发执行时,这些对象的同步由应用负责。
Key:有名字和类型的数据
pulse.NewKey[string]("summary") 表示名为 summary 的字符串数据。同名、同类型的 Key 等价;在一张图内,同名键不能混用不同类型。未初始化的零 Key 不能用作输入或端口。
节点通过 Requires 声明读取权限,通过 Provides 声明写入权限。读取未声明的输入或写入未声明的输出会返回 ErrUndeclared。不同类型的声明通过 pulse.Deps 组合。
每条数据只有一个来源:它可以是声明给宿主的入口,也可以由一个节点产生。重复来源在装配时被拒绝。多个下游节点可以读取同一个普通值;多个 channel 读者会竞争流元素,流需要额外规则,见流式处理。
端口:图的公共接口
Key 用于内部读写,端口决定宿主和父图能访问哪些数据:
DeclareInput声明可从外部提供的输入;本轮值通过Inputs提供。DeclareOutput声明可从本轮执行读取的输出。- 子图通过声明的端口接线,内部 Key 不会自动成为公共接口。
一个 Key 同时声明为入口与出口时,可以形成透传接口。如果它是可选入口,本轮没有提供且没有节点依赖它,结束后仍可能是 pending。这是明确的正常状态,不会被改写为跳过。
数据槽位的三种状态
Key 标识数据;一次 Execution 中对应的槽位保存这次数据的到达情况。
| 状态 | 含义 | 是否已经到达 |
|---|---|---|
pending | 尚未提供结果 | 否 |
ready | 已提供一个值,包括合法零值 | 是 |
skipped | 已明确表示这次没有值 | 是 |
ready 与 skipped 都会结束等待。0、空串和合法 nil 值不能代替缺失状态;它们可以是业务有效值。具体 nil 输入限制见输入与输出。
Set 写入值,Skip 写入正常缺值。同一槽位的同态重复写是幂等首写:再次 Set 不覆盖原值,再次 Skip 不改变状态。Set 与 Skip 相互冲突。宿主向 Inputs 重复提供同一键则会在 Seed / SkipSeed 时被拒绝。
成功返回的节点,其尚未写入的声明输出会自动跳过。需要选择一路时,应写入被选中的输出,并明确跳过其它输出;分支指南给出完整例子。
节点何时运行
普通节点遵守以下规则:
- 没有输入依赖的节点可以开始运行。
- 有输入的节点等待所有输入到达;到达可以是
ready或skipped。 - 所有输入到达后,只要至少一项为
ready,节点就进入处理函数;全部为skipped时,节点跳过处理函数,输出也跳过。
例如摘要有值、标签跳过时,报告节点仍可进入函数。它需要用 TryGet 或 Batch 明确收集有值的部分。如果业务要求摘要和标签缺一不可,在函数里返回 WaitAll 的结果,选择整体跳过。
这不是“收到第一项就立即处理”。汇聚节点始终等各路都表明结果,再按声明的策略处理。分支与汇聚说明两种策略及对应 API。
跳过、失败与取消
跳过是正常结果。 节点可以跳过一项输出,或通过 NoValue / 跳过结果结束整个节点。整张图全部跳过也可以成功;Wait 不把正常跳过作为错误返回。
真实错误是执行失败。 节点返回非跳过错误时,Pulse 记录错误并取消本次执行,使等待者和协作处理函数退出。节点 panic 会被转换为执行错误。错误不会被静默转成缺值。
取消需要业务协作。 在 HTTP、数据库、循环和阻塞操作中使用 rc.Context()。Go 无法安全终止一个忽略取消的业务函数;Wait 和 Timeout 仍需等待函数退出。
封口与复用
Validate 成功,或某次 Execution 成功启动时,会封口整棵定义树,包括已挂载的子图。封口固定节点和端口,避免执行中发生结构变化。
校验失败不会封口,可补全定义后重试。封口后的 Graph 仍可执行,也可挂载到其它图;它冻结的是定义,并没有被某次执行消耗。
若同一个子图挂在两个位置,每个位置创建独立子 Execution。挂载位置还会出现在观测的 Site.Path 中,便于区分复用模块的不同实例。
切面与观测
Aspect 包住节点的等待与处理过程,适合显式重试和超时。先声明的切面位于外层。Retry 为每次尝试暂存输出,失败且将重试时丢弃这些写入;这不回滚外部副作用。重试与取消解释组合顺序和限制。
Topology 描述静态结构,Snapshot 描述一轮执行的状态,Observer 提供事件。状态快照不携带业务值,读取视图也不会推进执行。业务输出通过声明出口读取,事件通过可选 observe 包接入日志。