低代码 Runtime 策略注入:让表达式引擎真正可扩展

5 阅读3分钟

一、背景

在低代码平台中,Expression Engine(表达式引擎) 是 Runtime 的核心能力之一。
它负责将 Schema 中的动态表达式(如 {{user.name}})解析为运行时真实数据,从而驱动 UI 渲染。

二、概述

在我的低代码平台中,Expression Engine 的执行不是简单的函数调用, 而是由 Runtime 驱动的一套完整运行时执行流程。

🔁 最小闭环

    graph TD
    Runtime[PreviewRuntime] -->|选择evaluator| resolveProps
    resolveProps -->|遍历props| resolveTemplate
    resolveTemplate -->|解析模板| evaluateExpression
    evaluateExpression -->|返回结果| resolveTemplate
    resolveTemplate --> resolveProps
    resolveProps --> Runtime

🎯 Runtime 的核心职责

PreviewRuntime 在这个闭环中承担的关键角色:
1️⃣ 调度中心
统一驱动整个执行流程

2️⃣ 策略控制点
决定本次执行使用哪一种 evaluator(表达式执行策略)

3️⃣ 运行时上下文构建
提供表达式执行所需的数据环境


🔑 关键设计点

我在这里做了一个核心解耦:

  • resolveTemplate 不直接依赖 evaluateExpression
  • 表达式执行策略由 Runtime 决定
  • 并通过参数(evaluator)向下传递

三、核心原理

我的这套设计是将系统拆分为两类角色:

稳定流程层

负责执行通用流程,不关心具体策略:

  • resolveProps(遍历)
  • resolveTemplate(解析)

策略决策层

负责真正的策略选择,决定“这次到底怎么求值”:

  • PreviewRuntime(选择 evaluator)

🧩 执行模型

所以流程是:

Runtime 决定策略
↓
resolveProps 传递策略
↓
resolveTemplate 消费策略

这就是 “上层决定行为,底层只执行流程”


为什么这比直接调用更好?

如果 resolveTemplate 直接 import evaluateExpression ,会出现一个问题:

  • resolveTemplate 本来只是“模板壳处理工具”
  • 结果它变成了“绑定某一套表达式引擎的模板处理器”

而我现在这种写法,把职责切分的很清楚:

  • resolveTemplate 只负责识别“壳子”
  • resolveProps 只负责遍历 props
  • PreviewRuntime 才真正拥有“解析策略决定权”

也就是:把“表达式如何求值”从底层工具中抽离出来,交给 Runtime层 以策略注入的方式统一管理。
这就是典型的 策略注入


四、这个思路的优点

1. 职责更清晰

Runtime 决定“用什么策略”
工具函数只负责“执行流程”

2. 可替换性

以后完全可以替换 evaluator,而不用改“执行流程”
比如未来可能有:safeEvaluateExpression、designerEvaluateExpression等,都可以复用同一套流程

3. 易测试

比如测 resolveTemplate 时,无需跑完整表达式引擎,只要传一个 mock evaluator
这就把“流程测试”和“能力测试”拆开了

4. 扩展性

更利于扩展多运行态,比如 DesignerRuntime、PreviewRuntime 它们可以使用不同规则的 evaluator 解析:

  • 设计态 Runtime 传递一个轻量 evaluator
  • 运行态 Runtime 传递一个完整 evaluator

5. 开闭原则

对扩展开放,对修改关闭
新增一套 evaluator,不需要改 “执行流程”,只需要让 Runtime 改变注入策略即可


五、适用场景

我总结了可以在以下场景考虑这种方案:

1. 流程稳定,但策略可变

比如:日志上报流程固定(收集 → 格式化 → 上报),但策略不同:

  • 开发环境:console.log
  • 测试环境:mock上报
  • 生产环境:真实埋点系统

2. 多个模式切换

比如:低代码类编辑器系统表达式执行:

  • 设计态:需要高容错(轻量表达式解析)
  • 运行态:严格执行 + 高性能(完整表达式解析)

3. 上层需要控制行为,而不是在工具函数里

比如:缓存策略,同一个 getData 流程:

  • 有的场景需要强缓存
  • 有的需要实时请求

六、总结

策略注入 这种模式本质是“稳定流程 + 可替换策略”的设计思维。
通过策略注入,可以把“变化点”从流程中抽离出来, 从而提升系统的扩展性、可测试性和长期演进能力。