设计理念
本页解释 Pulse 的主要设计取舍,帮助你判断它是否适合项目,以及在遇到边界时应在哪一层解决问题。具体操作见使用指南,严格行为见参考。
依赖由数据声明推导
节点声明读取哪些 Key、产生哪些 Key。由于每个 Key 只有一个来源,Pulse 可以据此得到执行依赖、结构视图和静态诊断。
这样可以减少需要同时维护的内容:增加一项输入依赖时,修改节点声明即可。代价是工作流必须通过数据关系表达,不能把互相矛盾的手写边和读写声明并存。
拓扑用于检查与展示,运行由数据到达推进。静态校验仍会拒绝依赖环、缺少来源和无效端口;“数据驱动”并不意味着放弃结构检查。
定义复用,状态属于一次执行
长期保存一份 Graph,按请求创建新的 Execution,可以直接表达服务中的重复执行。每次输入、取消和失败都属于对应的执行,无需决定哪些槽位该被重置。
这份隔离只覆盖 Pulse 管理的运行状态。业务闭包、缓存、数据库连接和可变值仍属于宿主;需要共享时由宿主提供同步,需要跨进程保留时由持久层保存。
静态子图也遵循同一模型:复用的是定义,每个挂载位置和每次尝试创建自己的执行。
正常缺值有独立语义
分支未选中、可选处理没有结果,都可以是正常的业务结论。Pulse 用 skipped 表示明确没有值,用 pending 表示尚未到达,用 ready 表示有值。
这让零值保持业务含义,也让下游知道所有分支是否已结束。汇聚等各路到达后,默认允许处理已有值;要求全部有值的节点通过 WaitAll 明确选择更严格策略。
真实错误保留失败身份。正常缺值不会让整轮失败,存储故障等可靠错误也不会因为错误链中含跳过原因而被当成正常缺值。
尽早报告可判断的错误
缺少来源、重复来源、无效接线和依赖环都可以在执行前判断。将这些问题留到运行中,会表现为等待、超时或晚到的错误,难以定位。
因此装配入口、Validate 和执行启动各自检查当时可确定的事实。成功校验封口整棵定义树,使“检查过的定义”与“实际执行的定义”一致。校验失败则保留修改机会。
静态分析有明确边界。隐藏在业务回调中的等待、外部服务行为和应用共享数据无法仅凭声明证明,仍需业务设计与测试。
重试的保证以数据边界为限
Retry 给每次尝试独立的输出暂存。失败且将重试时丢弃该次写入,成功或正常跳过时提交,避免下游收到失败尝试的结果。
这份保证针对 Pulse 的数据。若节点已经扣款、发出通知或提交外部事务,丢弃槽位写入不会撤销这些动作。应用应根据业务使用幂等键、事务、outbox 或补偿。
检查点层进一步要求存储确认后才发布输出。但“节点成果已保存”仍不等于“外部系统只被调用一次”,恢复时未确认业务体可能重做。
观测围绕实际执行事实
静态 Topology、执行 Snapshot 和事件记录回答不同问题:结构是什么、现在处于什么状态、发生过什么。
引擎更新状态后发事件,观察者不会成为隐式控制流。默认快照和记录不包含业务值;宿主读取结果使用声明出口,附加业务维度使用显式属性。
快照逐项读取,因此是采样视图而非全图原子切面。需要持久审计或稳定历史时,应由宿主的记录与存储层负责。
分层保持职责清楚
| 层 | 负责的事 |
|---|---|
| Pulse 核心 | 单进程执行、数据流、组合、校验、状态与检查点桥 |
observe | 结构化记录、日志出口、渲染和缓冲 |
yaml | 声明式拓扑与类型/工厂装配 |
| Durable | 检查点编码、存储、所有权和恢复 |
| 应用 | 业务 IO、共享对象、幂等、外部事务和版本策略 |
核心只使用标准库,可选层单向依赖核心。Durable 的本机检查点恢复已实现;持久等待、任务领取与分布式故障接管属于后续方向。