变量引擎核心概念可以通过下图串起来理解:
为了降低抽象程度,可以先记住一个真实案例:
「批处理节点」读取「上游 HTTP 节点」的数组输出 → 遍历得到
item→ 在子节点里继续使用item。
这个过程涉及的所有名词都在下文出现,阅读时可随时对照。
变量是在设计态定义、在运行态求值的数据容器。进一步了解可参考 变量介绍。
在流程设计中,变量只关注定义,不关注值。变量的值在流程的运行时才会被动态计算。
作用域(Scope)是一种容器:容器内聚合了一系列变量信息,同时维护了与其他作用域的依赖关系。一句话概括:作用域决定「谁可以访问哪些变量」。
作用域的范围可以根据业务场景的不同约定,常见的三类如下:
| 场景 | 示例 |
|---|---|
| 流程里节点可以约定为作用域 | ![]() |
| 全局变量侧边栏也可以约定为作用域 | ![]() |
| 界面编辑里组件(含变量)可以约定为作用域 | ![]() |
作用域通过 AST 存储变量信息。可以把它当作「变量信息」的树形结构:每个节点描述一个声明、类型或表达式。
通过 scope.ast 可以访问作用域内的 AST 树,从而对变量信息进行 CRUD 操作。
ASTNode 是变量引擎中用于存储变量信息的基本信息单元。它可以为各种变量信息建模:
VariableDeclaration ,用于声明新变量。StringType,用于表示 String 类型。KeyPathExpression,用于对变量的引用。ASTNode 可以嵌套形成树(AST),表示复杂的变量结构。ASTNode 可以与 JSON 格式(ASTNodeJSON)相互转换,以便存储或传输。ASTNode 基类来添加 新功能。ASTNode 值的变化会触发事件,从而实现响应式编程模式。ASTNodeJSON 是 ASTNode 的纯 JSON 序列化表示。通常我们会在设计端构造它,再交由变量引擎实例化。
最关键的字段是 kind,用于表示 ASTNode 的类型:
用户在使用变量引擎时,通过 ASTNodeJSON 描述变量信息,然后通过变量引擎实例化为 ASTNode,并将其添加到作用域中。
ASTNodeJSON 和 ASTNode 的关系,类似于 React 中 JSX 和 VDOM 的关系
ASTNodeJSON 通过变量引擎实例化为 ASTNodeJSX 通过 React 引擎实例化为 VDOMJson Schema 是一种用于描述 JSON 数据结构的格式:
Json Schema 只描述了变量的类型信息,而 ASTNodeJSON 还可以包含变量的其他信息(如:变量的初始值)。ASTNodeJSON 可以通过变量引擎实例化为 ASTNode,从而实现响应式监听等能力。Json Schema 擅长描述 Json 的类型,而 ASTNodeJSON 可以通过自定义扩展定义行为更复杂的信息。在技术选型上,变量引擎内核需要更强大的扩展与表达能力,因此需要用 ASTNodeJSON 来描述更丰富更复杂的变量信息,如:通过定义变量的初始值,实现变量类型的动态推导 + 自动联动。
不过 Json Schema 作为业界通用的 JSON 类型描述格式,在易用性、跨团队沟通以及生态(如 ajv、zod)上更有优势。因此我们在物料库中大量使用了 Json Schema,来降低大家的上手成本。
变量引擎提供了 ASTFactory,可以类型安全地创建 ASTNodeJSON:
声明 = 标识符(Key) + 定义(Definition)。在设计态中,声明是一种存储标识符与变量信息的 ASTNode,是变量系统的最小「可被引用」单元。
变量声明 = 标识符 + 变量定义(类型 + 初始值)
函数声明 = 标识符 + 函数定义(函数入参出参 + 函数体实现)
结构体声明 = 标识符 + 结构体定义(字段 + 类型)
标识符是声明的索引,用于访问声明中的定义。标识符找到变量的类型定义,从而可以进行类型检查。变量引擎目前只提供了变量字段声明(BaseVariableField),并基于此扩展了变量声明(VariableDeclaration)和属性声明(Property)两种声明。
BaseVariableField)= 标识符 + 变量字段定义(类型 + 元信息 + 初始值)VariableDeclaration)= 全局唯一标识符 + 变量定义(类型 + 元信息 + 初始值 + 作用域内排序)Property)= Object 内唯一标识符 + 属性定义(类型 + 元信息 + 初始值)类型用于约束变量值的范围。在设计态中,类型也是一种 ASTNode。理解类型有助于掌握「变量能装什么」以及「表达式返回什么」。
变量引擎内置了 JSON 的基础类型:
StringType:字符串IntegerType:整数NumberType:浮点数BooleanType:布尔值ObjectType:对象,可下钻 Property 声明。ArrayType:数组,可下钻其他类型。同时新增了:
MapType:键值对,键和值都可以进行类型定义。CustomType:由用户进行自定义扩展,如日期、时间、文件类型等。表达式输入 0 个或者多个变量,并通过特定方式进行计算,返回一个新的变量。设计态只描述「依赖了谁」和「推导出的类型」,运行态负责真正的值计算。
而在设计态中,表达式是一种 ASTNode,建模中我们只需关注:
假设我们有一个用 JavaScript 代码描述的表达式 ref_var + 1
表达式使用了哪些变量声明 ?
ref_var 标识符对应的变量声明表达式的返回类型是怎么推导的 ?
ref_var 的类型为 IntegerType,则 ref_var + 1 的返回类型为 IntegerTyperef_var 的类型为 NumberType,则 ref_var + 1 的返回类型为 NumberTyperef_var 的类型为 StringType,则 ref_var + 1 的返回类型为 StringType
图中展示了一个常见的例子:批处理节点引用前序节点的输出变量,对其进行遍历处理,得到一个 item 变量。其中 item 的变量类型会随着前序节点输出变量的类型而自动变化。
这个例子的 ASTNodeJSON 可表示为:
变量的推导链路如下:
作用域链(Scope Chain)定义了一个作用域可以引用哪些作用域的变量。可以把它理解成「可读变量的白名单」。变量引擎提供了抽象类,具体业务可以根据实际编排形式实现自定义的作用域链。
变量引擎内置了自由布局作用域链和固定布局作用域链两种作用域链实现。
依赖作用域 = 当前作用域可以访问哪些作用域的输出变量。
可以通过 scope.depScopes 访问作用域的依赖作用域。
覆盖作用域 = 哪些作用域可以访问当前作用域的输出变量。
可以通过 scope.coverScopes 访问作用域的覆盖作用域。
FlowGram 在画布中定义了以下几种特殊的作用域:
又称节点公开作用域,作用域可以访问上游节点的节点作用域的变量,同时其输出变量声明也可以被下游节点的节点作用域访问。
节点作用域 可以通过 node.scope 来设置和获取,它的作用域链关系如下图所 示:
在默认的作用域逻辑中,子节点的 节点作用域 输出变量不可被父节点的下游节点 访问。
节点私有作用域的输出变量只能在当前节点的节点作用域及其子节点的节点作用域中访问。类似编程语言中私有变量的概念。
节点私有作用域 可以通过 node.privateScope 来设置和获取,它的作用域链关系如下图所示:
全局作用域的变量能被所有节点作用域和节点私有作用域访问,但是自身不依赖其他作用域。适用于配置、常量、环境变量等公共信息。
全局作用域的设置方式详见输出全局变量,他的作用域链关系如下图所示:

变量引擎设计上遵循 DIP(依赖反转)原则,按照代码稳定性、抽象层次以及与业务的距离分为三层:
抽象层是最稳定的一层,定义了 ASTNode、Scope、ScopeChain 等核心接口,为上层实现提供扩展约束。
这一层包含更贴近业务的实现,易随产品演化调整。引擎内置了一批稳定的 ASTNode 节点和 ScopeChain 实现;当业务需要时,可以通过依赖注入注册新的节点或覆盖已有实现。
最外层通过外观模式(Facade)提升易用性,将复杂能力封装成「物料」给使用者直接复用。