复杂业务规则太多?我们搭了一个规则可视化配置平台
一、为什么需要这样一个平台
在业务系统里,变化频繁且复杂度容易失控的部分,往往不是底层能力,而是各种业务规则。
比如是否允许开票、是否允许解绑、地址编辑是否允许、业务开关如何选择、风控命中策略如何调整等。这些规则通常有几个共同特点:
-
判断条件多,容易随着城市、车型、业务线、渠道、订单状态等维度持续变化。
-
规则变更频繁,但直接改代码发版周期长,直接改配置中心又缺少结构化约束。
-
变更前难验证,变更后难对比,出了问题难追溯、难回滚。
-
规则责任人、审批人、影响范围和发布过程缺少统一的平台化管控。
过去很多复杂业务逻辑会沉淀成代码分支或配置中心条目。代码分支稳定但灵活性不足,配置中心变更灵活,但在规则表达、验证和变更管控方面能力有限。
规则可视化配置平台要解决的核心问题,就是把"散落在代码和配置里的业务判断逻辑"抽象成可视化、可验证、可审批、可灰度、可追溯的策略资产。
典型问题
| 问题 | 影响 | 平台化后的解决方式 |
|---|---|---|
| 配置变更未通知责任人 | 相关业务方无法提前感知风险 | 发布审批、负责人通知、发布卡片 |
| 配置结构自由,容易写错 | 线上规则不符合预期,可能引发资损或舆情 | 可视化编排、字段类型约束、测试用例验证 |
| 回归成本高 | 需要构造真实业务链路才能验证 | 页面直接构造入参,离线执行规则链 |
| 上线缺少小流量验证 | 一次性全量生效风险高 | 双跑对比、按比例灰度切流 |
| 变更不可追溯 | 问题定位和回滚困难 | 快照、发布记录、审批记录、回滚能力 |
二、它是什么
规则可视化配置平台是一套面向多业务域的"规则配置、策略编排、发布治理和运行时执行平台"。
它把原本写在代码里的 if/else、switch、策略选择、结果输出等逻辑,拆成可复用的字段、组件和链路。业务人员或研发人员可以在管理端通过可视化方式编排规则,平台再把规则发布成机器可执行的规则链,下发到业务服务运行时执行。
可以从四个层面理解:
-
字段/选项:规则里可以使用的业务变量,例如订单字段、运单字段、账单字段、业务入参和中间结果。
-
组件:一段独立业务判断或结果输出,类似代码里的一个方法。
-
链路:多个组件按照 EL 表达式编排成的完整规则逻辑,类似一段由多个方法组合出来的业务流程。
-
运行时:业务服务引入 SDK 后,根据链路 ID 和入参执行规则,得到结构化结果。
它不是单纯的配置页面,也不是一个所有请求都要远程调用的规则服务。更准确地说,它由四层能力组成:
| 层次 | 角色 | 职责 |
|---|---|---|
| 管理层 | 规则管理端 | 规则空间、字段、组件、链路、测试用例、审批、发布、对比、切流、回滚 |
| 配置服务层 | 配置中心服务端 | 规则配置持久化、发布到配置中心、提供批量查询 |
| 配置消费层 | 配置消费 SDK | 业务服务启动拉取配置、本地缓存、监听变更、通知运行时 |
| 运行时层 | 规则执行 SDK | 解析规则链、注册执行模型、热更新、执行规则、支持对比和灰度 |
整体主链路是:
Plaintext
管理端配置规则
-> 审批通过后发布
-> 配置服务端落库并推送配置变更
-> 配置消费 SDK 在业务服务内拉取和监听配置
-> 规则执行 SDK 加载规则链
-> 业务服务调用 SDK 执行规则
有了这个基本定义之后,再回头看最初的问题,就能更清楚地看到平台的价值:它不是简单为配置增加一个管理页面,而是补齐复杂规则在表达、验证、发布和运行时治理上的缺口。
三、它解决了什么问题
1. 从"配置字符串"升级为"结构化规则"
配置中心更适合存储简单配置,但复杂策略往往需要条件、分支、结果、嵌套链路和字段类型。规则可视化配置平台把这些内容抽象成空间、类别、选项、组件、链路和测试用例,让业务策略具备结构化表达能力。
| 能力 | 普通配置中心 | 规则可视化配置平台 |
|---|---|---|
| 表达方式 | 字符串 / JSON | 字段 + 组件 + 链路 + EL |
| 复杂分支 | 依赖人工维护 JSON | 可视化编排 |
| 变更验证 | 通常依赖业务回归 | 页面测试用例直接执行 |
| 发布管控 | 基础审批和通知 | 负责人审批、变更明细、发布快照 |
| 灰度能力 | 依赖业务自行实现 | 双跑对比、按比例切流 |
| 回滚追溯 | 依赖配置历史 | 链路快照、发布快照、回滚 |
| 领域隔离 | namespace 维度较粗 | 空间、权限组、业务类别 |
2. 降低业务变更成本
对于频繁调整的业务策略,不再要求每次都修改代码、发版、等待上线。规则维护人员可以在页面上调整条件和输出结果,研发主要负责沉淀可复用字段、组件和运行时能力。
3. 降低线上风险
规则发布前要求测试用例验证,发布中经过审批,发布后可以先双跑观察差异,再通过比例控制小流量切流。出现问题时,可以基于发布快照快速回滚。
4. 提升规则复用和沉淀
订单、运单、账单等业务字段可以统一维护,组件可以复用,链路可以引用子链路。随着落地场景增加,平台会逐步沉淀为跨业务域的策略资产库。
这些收益背后,对应的是一组必须具备的基础能力。也就是说,仅有"可视化配置"不足以支撑完整的规则治理,平台还需要把验证、审批、灰度、回滚和资产复用一起纳入设计。
四、平台需要具备哪些核心能力
如果一个规则平台只解决"把规则放到页面上配置",价值其实有限。更核心的问题是:规则如何表达、如何验证、如何发布、如何在线上安全生效,以及出问题时如何快速定位和回滚。
因此平台核心能力可以拆成几类:
1. 可视化编排
平台把规则拆成字段、组件和链路。字段负责描述规则可以使用的数据,组件负责承载独立判断或输出逻辑,链路负责把多个组件按顺序、条件、分支组合起来。
这样一来,复杂规则不再是一段难以理解的 JSON 或一堆散落的配置项,而是变成了可以阅读、复用和审查的结构化流程。
2. 测试用例验证
规则上线前必须能被验证。平台支持直接在页面构造入参并执行规则链,不需要完整走一遍真实业务接口。对于复杂命中条件,测试用例可以沉淀为规则资产的一部分,后续每次变更都可以复用。
3. 发布审批和变更追溯
规则虽然是配置,但影响不亚于代码。平台在发布前保留审批流程,审批时展示变更明细;发布后保留链路快照和发布快照。这样既能控制变更风险,也能在问题发生后快速定位"谁在什么时候改了什么"。
4. 双跑对比和灰度切流
对于高风险规则,平台支持新旧规则双跑。双跑阶段可以同时执行线上主链和待发布链路,只观察结果差异,不影响真实业务结果。确认差异符合预期后,再通过比例控制逐步把流量切到新规则。
5. 快速回滚
每次发布都会生成发布快照。出现问题时,可以基于历史快照恢复上一版本,并重新同步到配置中心。相比人工回滚配置,快照化回滚更可靠,也更适合复杂链路。
6. 字段和组件资产化
随着接入场景变多,业务字段、判断组件、结果组件会逐步沉淀下来。后续配置新规则时,不必每次从零开始,只需要复用已有字段和组件,再编排新的链路。
能力清单说明了平台要解决的问题,但真正落地时,还需要进一步回答"这些能力如何拆分、如何部署、如何运行"。这就进入到平台的核心设计。
五、核心设计思路
这一部分先不展开代码细节,而是先看几个关键设计取舍。它们决定了平台后续是否能够支撑复杂规则、频繁变更、线上灰度和本地高性能执行。
1. 管理面和运行面分离
规则可视化配置平台没有把所有能力做成一个大服务,而是拆成管理面、配置面和运行面:
-
管理面:负责规则资产管理、测试用例、审批、发布、回滚和权限控制。
-
配置面:负责规则配置的持久化、版本管理、配置推送、客户端拉取、本地缓存和变更通知。
-
运行面:以 SDK 的方式嵌入业务服务本地执行规则,不需要每次执行都远程调用规则中心。
这样设计的好处是:规则执行链路短、性能稳定;规则发布链路又能复用现有配置中心和审批体系。
2. 以 Chain JSON 作为规则发布产物
管理端保存的是编辑态模型,包括 rule_chain、rule_component、rule_chain_snapshot、rule_chain_publish_snapshot 等。发布时,管理端把链路、组件、EL、参数类型、灰度比例等信息组装成 SDK 可识别的 Chain JSON。
发布到配置中心时使用固定约定:
Plaintext
module = rules
key = chainKey
value = Chain JSON
业务服务只需要配置自己关心的 chainKeys,运行时 SDK 会自动从配置中心拉取对应规则。
3. 以 LiteFlow 作为执行内核
规则可视化配置平台的运行时没有重复造一套流程执行器,而是基于 LiteFlow 完成链路执行。SDK 负责把管理端发布的 Chain 和 Component 转换成 LiteFlow 可以识别的 Chain、EL 和 Script Node。
运行时核心对象包括:
-
ChainHelper:负责规则链和脚本组件的加载、重载、卸载和本地缓存。 -
RuleConfigParser:负责从配置缓存读取规则、监听变更,并触发热更新。 -
ChainExecutor:业务执行入口,负责主链执行、对比链双跑、按比例切流和结果封装。 -
ChainContext:规则执行上下文,提供当前链路、入参、中间计算结果等。 -
ChainResultContext:规则结果上下文,脚本组件通过它写入最终输出。
4. 发布前验证、发布中审批、发布后灰度
规则变更风险主要来自"配置正确性"和"线上影响范围"。因此设计上把风险控制拆成三个阶段:
| 阶段 | 控制手段 | 目的 |
|---|---|---|
| 发布前 | 测试用例、脚本校验、链路执行校验 | 确认规则逻辑符合预期 |
| 发布中 | 负责人审批、变更明细、权限控制 | 确认变更被正确的人审核 |
| 发布后 | 双跑对比、按比例切流、发布通知、回滚 | 小流量验证,发现问题快速止损 |
5. 支持热更新但保留兜底同步
规则配置发布后,配置服务端会写入数据库并推送配置变更。业务服务内的配置消费 SDK 一方面监听配置中心变更,另一方面也会定时拉取配置兜底,避免只依赖推送导致配置不一致。
热更新路径如下:
Plaintext
配置中心变更
-> 配置监听器
-> 本地配置仓储更新缓存
-> 发送变更事件
-> 规则解析器监听到变化
-> ChainHelper reload / unload
-> LiteFlow FlowBus 更新规则链
上面讲的是设计取舍,接下来再往下一层,看这些设计在系统内部是如何协作的:规则如何从管理端流到运行时,模型如何转换,最终又如何通过 LiteFlow 执行。
六、核心原理
前面讲的是平台为什么要做,以及它提供了哪些能力。进入实现层面后,核心问题可以拆成三个:
-
规则配置如何在各模块之间流转:分别由哪些模块负责编辑、发布、缓存和执行。
-
规则在系统里如何被表达:页面上的空间、选项、组件、链路,最终会对应哪些系统模型。
-
规则如何被真正执行:配置化模型如何转换成可执行流程,业务请求进入后如何得到结果。
这一节按「架构与模型 → 发布与治理 → 拉取、编译与执行」三条主线展开,顺序贴近一条规则从配置到上线的真实路径。
1. 整体架构
整体架构图关注的是"规则如何流转"。一条规则从页面配置开始,经过审批、发布、配置中心下发、客户端缓存,最后进入业务服务本地运行时执行。核心设计取舍是:管理和发布集中化,执行本地化。
2. 模型关系
模型关系图关注的是"规则如何被表达"。管理端保存编辑态模型;发布时组装成 Chain JSON 写入配置中心;业务服务拉取后反序列化为运行时 Chain、Component,再交给执行器处理。
3. 规则发布全流程
规则发布是管理面到运行面的关键桥梁:ChainDTO → Chain JSON → RPC 写入配置服务端 → Apollo 推送到各业务 Pod。
3.1 链路状态

3.2 发布逻辑

4. 发布审批流程

5. 双跑对比与灰度切流
平台约定 {chainKey}Compare 作为影子链路 Key,在不影响主链结果的前提下验证新规则。
5.1 管理端发布逻辑

5.2 运行时执行分支
流程与代码对照(运行时)
| 流程步骤 | 关键入口 |
|---|---|
| execute chainKey | ChainExecutor.execute |
| 加载 chainKeyCompare | ChainHelper.getByChainCode(appendCompareSuffix(key)) |
| 版本不同且 flowPercent 为空 → 双跑 | needCompare → doExecute(main) + compare(...) |
| 版本不同且命中比例 → 切流 | needFlow + randomPercent <= flowPercent |
| 版本相同 → 正常执行 | doExecute(main) |
| JsonDiff 记录差异 | compare() 内 JsonDiff.asJson(old, new) |
关键代码(规则引擎 SDK · ChainExecutor)
Java
// executeAndComparedAndFlow --- 对应流程图三条分支
Chain chainCompare = ChainHelper.getByChainCode(appendCompareSuffix(chain.getKey()));
if (needCompare(chain, chainCompare)) { // 版本不同 && flowPercent == null
ChainExecuteResult result = doExecute(chain, params);
compare(chain, result, chainCompare, params); // 影子执行 + JsonDiff,结果仍以主链为准
return result;
}
if (needFlow(chain, chainCompare) && randomPercent <= chainCompare.getFlowPercent()) {
return doExecute(chainCompare, params); // 切流走新链
}
return doExecute(chain, params); // 正常执行
// needCompare / needFlow 判断条件
boolean needCompare = !main.version.equals(compare.version) && compare.flowPercent == null;
boolean needFlow = !main.version.equals(compare.version) && compare.flowPercent != null;
| 阶段 | 状态 | Compare 链 flowPercent | 运行时行为 |
|---|---|---|---|
| 对比 | COMPARE | null | 主链返回;影子执行 Compare 并 JsonDiff |
| 切流 | FLOW | 1~100 | 按比例走 Compare 链 |
| 全量 | PUBLISHED | null(同版本) | 不再对比/切流 |
6. 业务侧规则配置拉取
各业务服务引入配置消费 SDK 后,通过三条路径保持配置一致。

流程与代码对照
| 流程步骤 | 模块 | 关键入口 |
|---|---|---|
| 注册 chainKey + Compare | 规则引擎 SDK | LiteflowPropertyAutoConfiguration.parseKeys |
| 服务启动 → RPC 拉取 | 配置消费 SDK | TradeConfigStarter.start → TradeConfigRepository.sync(STARTER) |
| batchFetchConfig RPC | 配置消费 SDK | TradeConfigRepository.sync → TradeConfigClient.batchFetchConfig |
| releaseVersion fallback | 配置服务端 | TradeConfigService.getTradeConfig |
| Apollo 推送 → 解析变更 | 配置消费 SDK | ApolloListener.listen → needUpdate → updateConfig |
| 定时兜底同步 | 配置消费 SDK | TradeConfigSyncSchedule → sync(SCHEDULE) |
| 写入本地缓存 | 配置消费 SDK | fireConfigChange → TradeConfig.putValue |
| 变更通知 → 热加载 | 规则引擎 SDK | TradeConfigNotify → ApolloParseHelper.listen → ChainHelper.reload |
关键代码
Java
// [启动] TradeConfigStarter --- 对应「服务启动」触发源
@PostConstruct
public void start() {
tradeConfigRepository.sync(SyncSource.STARTER); // RPC 拉取
apolloListener.listen(); // Apollo 监听
tradeConfigSyncSchedule.start(); // 定时兜底
}
// [启动/定时] TradeConfigRepository.sync --- 对应「batchFetchConfig RPC」
Lists.partition(keys, 10).forEach(batch -> {
condition.setReleaseVersion(ReleaseVersionUtil.getReleaseVersion());
tradeConfigFacade.batchFetchConfig(condition); // → 配置服务端 RPC
});
fireConfigChange(key, oldValue, newValue, syncSource); // → 写缓存 + notify
// [Apollo] ApolloListener --- 对应「Apollo 推送」分支
config.addChangeListener(event -> {
if (!needUpdate(tradeConfigKey, apolloKey)) return; // 灰度版本路由
tradeConfigRepository.updateConfig(key, apolloValue);
});
// [注册] 规则引擎 Starter --- 每个 chainKey 同时注册 Compare 链
keys.add(chainKey);
keys.add(appendCompareSuffix(chainKey));
// [fallback] 配置服务端 --- 灰度版本查不到时回退 default
if (tradeConfig == null && !DEFAULT.equals(releaseVersion)) {
return getTradeConfigWithReleaseVersion(list, key, DEFAULT);
}
7. 规则编译与热加载

流程与代码对照
| 流程步骤 | 模块 | 关键入口 |
|---|---|---|
| 读取 Chain JSON | 规则引擎 SDK | TradeConfig.getProperty(MODULE, chainKey) |
| parseChain + checkChain | 规则引擎 SDK | ChainHelper.parseChain |
| 注册 Script Node | 规则引擎 SDK | ChainHelper.loadChainScript → LiteFlowNodeBuilder |
| 注册 Chain EL | 规则引擎 SDK | ChainHelper.loadChain → LiteFlowChainELBuilder |
| 配置 UPDATE → 热加载 | 规则引擎 SDK | ApolloParseHelper.listen → ChainHelper.reload |
| 配置 DELETE → 卸载 | 规则引擎 SDK | ChainHelper.unload(引用计数归零) |
关键代码(规则引擎 SDK)
Java
// [启动] ApolloParseHelper.initFromApollo --- 对应「读取 → 解析 → 注册」
for (RuleEngineKey key : keys) {
String chainJson = TradeConfig.getProperty(MODULE, key.getChainKey());
Chain chain = ChainHelper.parseChain(chainJson); // parseChain + checkChain
ChainHelper.load(chain);
}
// [load] ChainHelper --- 对应「注册 Script Node + Chain EL」
public static void load(Chain chain) {
loadChainScript(chain, chain); // LiteFlowNodeBuilder.createScriptNode().build()
loadChain(chain, chain); // LiteFlowChainELBuilder.createChain().setEL(...).build()
}
// [热更新] ApolloParseHelper.listen --- 对应 fork 两条路径
TradeConfig.addListener(event -> {
switch (changeType) {
case ADD, UPDATE -> ChainHelper.reload(newChain); // unload 旧链 → load 新链
case DELETE -> ChainHelper.unload(chain); // 引用计数归零后卸载
}
});
// [reload] ChainHelper.reload
if (oldChain != null) unload(oldChain);
load(chain);
8. 规则执行原理

流程与代码对照
| 流程步骤 | 模块 | 关键入口 |
|---|---|---|
| ChainExecutor.execute | 规则引擎 SDK | ChainExecutor.execute(chainId, params) |
| 对比 / 切流 / 正常路由 | 规则引擎 SDK | executeAndComparedAndFlow |
| 内存版本 vs 缓存版本兜底 | 规则引擎 SDK | tryReloadChain |
| 获取链路锁 | 规则引擎 SDK | chain.getLock().tryLock() |
| 构造上下文 | 规则引擎 SDK | new ChainContext() + new ChainResultContext() |
| LiteFlow 执行 | 规则引擎 SDK | flowExecutor.execute2Resp(chain.getCode(), params, contexts) |
| 封装结果 | 规则引擎 SDK | chainResultContext.getData() → ChainExecuteResult |
关键代码(规则引擎 SDK · ChainExecutor)
Java
// execute --- 入口,先走路由再执行
public ChainExecuteResult execute(String chainId, Map<String, Object> params) {
Chain chain = ChainHelper.getByChainCode(chainId);
return executeAndComparedAndFlow(chain, params); // 对比/切流/正常 路由
}
// doExecute --- 对应流程图「版本兜底 → 加锁 → 构造上下文 → LiteFlow 执行」
private ChainExecuteResult doExecute(Chain chain, Map<String, Object> params) {
chain = tryReloadChain(chain); // 内存 vs 缓存版本兜底
chain.getLock().tryLock(2, TimeUnit.SECONDS);
ChainContext ctx = new ChainContext();
ChainResultContext resultCtx = new ChainResultContext();
params = ChainHelper.handleParamsDataType(chain, params);
flowExecutor.execute2Resp(chain.getCode(), params, ctx, resultCtx);
return new ChainExecuteResult(resultCtx.getData());
}
// tryReloadChain --- 版本不一致时自动 reload
Chain chainReload = ChainHelper.parseChain(TradeConfig.getProperty(MODULE, chain.getKey()));
if (!chain.getVersion().equals(chainReload.getVersion())) {
ChainHelper.reload(chainReload);
return chainReload;
}
端到端主链路
Plaintext
页面配置 → 测试验证 → 审批 → compare → flow → publish
→ 配置服务端落库 + Apollo 推送
→ 配置 SDK 拉取/监听 → 本地缓存
→ 规则 SDK 编译加载 → LiteFlow 注册
→ ChainExecutor 执行业务请求
9. 从配置模型到执行模型:LiteFlow 如何承接规则
平台本身并不直接实现一套流程执行引擎,而是把"可视化配置出来的规则"转换成 LiteFlow 能识别的执行模型。这样可以把精力放在规则配置、发布治理、热更新和业务抽象上,底层流程编排、节点调度、上下文传递则交给成熟的流程框架完成。
LiteFlow 里最核心的几个概念是:
| LiteFlow 概念 | 在平台中的含义 |
|---|---|
| Chain | 一条规则链路,对应平台里的"链路" |
| EL | 链路编排表达式,描述组件的顺序、条件、分支和组合关系 |
| Node | 一个可执行节点,对应平台里的"组件" |
| FlowBus | LiteFlow 的运行时注册表,保存已经加载的 Chain 和 Node |
| FlowExecutor | LiteFlow 的执行入口,根据 chainId 执行对应链路 |
| Context | 执行上下文,用来在多个组件之间传递入参、中间结果和最终结果 |
9.1 从可视化链路到 LiteFlow Chain
管理端配置出来的链路,最终会被发布成一份 Chain JSON。这份 JSON 里包含链路编码、EL 表达式、组件列表、子链路、参数类型、版本号、是否启用、灰度比例等信息。
一个简化后的结构可以理解为:
JSON
{
"key": "address_edit_strategy",
"el": "THEN(checkCity, checkOrderStatus, buildResult)",
"enable": true,
"version": 12,
"flowPercent": 10,
"cmpList": [
{
"code": "checkCity",
"componentClass": "SCRIPT",
"script": "cityId in supportCityList"
},
{
"code": "checkOrderStatus",
"componentClass": "SCRIPT",
"script": "orderStatus == 'WAIT_ACCEPT'"
},
{
"code": "buildResult",
"componentClass": "SCRIPT",
"script": "setData(chainResultCtx, 'allowEditAddress', true)"
}
]
}
运行时 SDK 启动后,会从配置缓存中读取这份 Chain JSON,然后做两件事:
-
把链路里的
el注册成 LiteFlow 的 Chain。 -
把链路里的每个组件注册成 LiteFlow 的 Node。
也就是说,业务侧看到的是"配置了一条规则链",LiteFlow 底层看到的是"一条可执行 Chain + 多个可执行 Node"。
9.2 EL 表达式负责描述编排关系
EL 是 LiteFlow 的编排语言。平台里的"链路画布"最终会转换成 EL 表达式,交给 LiteFlow 解析。
例如顺序执行可以表达为:
Plaintext
THEN(checkCity, checkOrderStatus, buildResult)
带条件分支时,可以表达为:
Plaintext
IF(checkRisk, denyResult, THEN(checkCity, buildResult))
这层转换非常关键。可视化平台对用户暴露的是流程图、组件和字段;运行时真正执行的是 LiteFlow 的 EL。这样既能提升配置侧的易用性,又能保持执行侧的稳定性。
9.3 组件会被转换成 LiteFlow Node
平台里的组件有两种典型实现方式:
-
Java 组件:提前写好并注册到 Spring 容器里,适合稳定且复杂的逻辑。
-
脚本组件:发布时带着脚本内容下发,运行时动态注册成脚本节点,适合频繁变化的判断逻辑。
对于脚本组件,运行时会把组件编码、脚本内容、脚本语言注册到 LiteFlow 的 Node 中。组件执行时,可以从上下文里拿到入参,也可以把结果写回上下文。
比如一个脚本组件可以做这样的事情:
Plaintext
读取 cityId、orderStatus
-> 判断是否命中条件
-> 把判断结果写入 chainCtx 或 chainResultCtx
在这个过程中,LiteFlow 负责"什么时候执行这个节点",平台负责"这个节点里具体执行什么业务逻辑"。
9.4 FlowBus 是运行时规则注册表
LiteFlow 运行时会把 Chain 和 Node 注册到 FlowBus。可以把 FlowBus 理解为一个内存中的规则执行注册表:
-
chainMap保存链路定义。 -
nodeMap保存节点定义。 -
执行时根据 chainId 找到对应 Chain。
-
Chain 再根据 EL 找到需要执行的 Node。
平台的热更新能力,本质上也是围绕 FlowBus 做的。当配置中心推送新规则后,运行时会解析新的 Chain JSON,再执行:
Plaintext
卸载旧 Chain / 旧 Script Node
-> 注册新 Chain / 新 Script Node
-> 后续请求自动使用新版本规则
为了避免执行过程中链路被并发修改,运行时通常会在链路维度做必要的锁控制,并通过版本号判断本地缓存是否需要重新加载。
9.5 FlowExecutor 负责真正执行链路
业务服务最终不会直接操作 FlowBus,而是调用平台封装的执行入口。执行入口会先做一些平台层逻辑:
-
根据 chainId 找到主链。
-
判断是否存在对比链。
-
判断是否需要双跑对比。
-
判断是否命中灰度比例。
-
构造规则执行上下文。
准备完成后,才会调用 LiteFlow 的 FlowExecutor 执行:
Plaintext
FlowExecutor.execute2Resp(chainCode, params, contextBeans)
LiteFlow 会根据 chainCode 找到对应 Chain,解析 EL,按顺序或分支执行 Node。每个 Node 执行时共享同一组上下文,所以前一个组件的计算结果可以被后一个组件继续使用。
9.6 上下文负责串起整个执行过程
规则执行不是简单地返回 true 或 false。许多业务场景需要读取原始入参、保存中间判断结果、输出结构化结果。因此平台在 LiteFlow 的上下文机制上封装了两个核心上下文:
-
ChainContext:保存当前链路、请求入参、中间计算结果,组件可以从这里读取变量或查找其他组件结果。
-
ChainResultContext:保存最终输出结果,结果组件会把业务需要的返回值写到这里。
以前面的地址编辑规则为例:
Plaintext
checkCity
-> 从 ChainContext 读取 cityId
-> 写入 citySupportEdit = true
checkOrderStatus
-> 从 ChainContext 读取 orderStatus
-> 写入 orderCanEditAddress = true
buildResult
-> 读取前两个组件结果
-> 向 ChainResultContext 写入 allowEditAddress、reason
最终 ChainExecutor 会把 ChainResultContext 里的数据封装成统一的执行结果返回给业务服务。
9.7 为什么不直接远程调用规则中心执行
这套设计选择"规则中心负责配置和发布,业务服务本地 SDK 负责执行",主要是出于几个考虑:
-
性能更稳定:规则执行发生在业务服务进程内,不需要每次请求都远程调用规则中心。
-
可用性更好:配置中心短暂不可用时,业务服务仍然可以使用本地缓存中的规则继续执行。
-
热更新足够快:配置变更通过监听和定时同步进入本地缓存,再触发 LiteFlow reload。
-
业务扩展更方便:业务服务可以传入自己的上下文 Bean,也可以注册本地 Java 组件。
所以 LiteFlow 在这里承担的是"本地流程执行内核"的角色,而规则可视化配置平台承担的是"规则资产管理、配置发布和运行时适配"的角色。上面第 6~8 节已从代码层面展开了配置拉取、编译热加载和执行细节,这里补充的是 LiteFlow 层的概念映射。
到这里,平台从"配置如何发布"到"规则如何执行"的主链路已经完整了。不过要理解这个平台为什么能够承载复杂业务规则,还需要回到配置侧的几个基础抽象:空间、类别、选项、组件、链路。它们决定了管理端是否易用,也决定了规则资产能否持续沉淀。
七、几个关键抽象
如果只看模型名,空间、类别、选项、组件、链路这些概念会显得比较抽象。更直观的理解方式,是把它们放到一个具体业务场景里看。
假设有这样一个规则:用户修改订单地址时,系统需要判断是否允许修改。这个判断可能受多种因素影响,比如城市、订单状态、用户类型、车型、是否已经接单、是否命中风险策略等。最终规则可能输出:
JSON
{
"allowEditAddress": true,
"reason": "当前城市和订单状态允许修改地址"
}
如果写在代码里,这类规则往往会变成连续的 if/else。如果写成普通配置,又容易演变成一段难以维护的 JSON。平台要做的事情,就是把这段业务判断拆成几个层次清晰、可以复用的抽象。
空间:先解决"谁管理谁"的问题
空间可以理解为规则资产的业务边界。比如地址编辑策略、开票策略、解绑策略可以分别放到不同空间里。每个空间有自己的字段、组件、链路和权限,互不影响。
这样可以避免规则平台变成一个缺少边界的公共配置区。地址编辑相关人员只管理地址编辑空间,开票相关人员只管理开票空间。权限边界清晰之后,规则配置才适合开放给更多业务方参与。
类别:让规则资产更容易查找
当一个空间里的字段和组件越来越多时,只靠名称搜索会很难维护。类别就是用来给规则资产分组的。
仍然以上面的地址编辑规则为例,可以把字段分成"订单字段""城市字段""用户字段""风控字段",把组件分成"状态判断""城市判断""结果输出"。后续配置人员想找"订单是否已接单"这样的字段,就不需要在所有字段里搜索,而是可以直接到订单字段分类下查找。
类别本身不参与规则执行,但它决定了平台是否好用。规则资产越多,分类越重要。
选项:把业务字段变成规则可识别的变量
选项是规则中可以使用的字段或变量。它既可以来自业务入参,也可以是组件执行后的中间结果。
比如地址编辑规则里可能会维护这些选项:
| 选项 | 含义 | 类型 |
|---|---|---|
cityId |
城市 ID | 数字 |
orderStatus |
订单状态 | 枚举 |
userType |
用户类型 | 枚举 |
isDriverAccepted |
司机是否已接单 | 布尔 |
allowEditAddress |
是否允许改地址 | 布尔 |
有了选项,规则配置就不再是"手写字段名"。配置人员可以从页面选择字段,平台也能基于字段类型做校验,减少把数字、字符串、布尔值混用导致的低级错误。
组件:把一段判断逻辑沉淀为可复用单元
组件可以理解为规则里的一个方法。它接收一些选项作为输入,经过判断后产出一个结果。
例如可以拆出几个组件:
-
判断城市是否支持地址编辑 :输入
cityId,输出citySupportEdit。 -
判断订单状态是否允许改地址 :输入
orderStatus、isDriverAccepted,输出orderCanEditAddress。 -
生成最终结果 :输入前面两个判断结果,输出
allowEditAddress、reason。
组件可以有不同类型:
-
布尔组件:回答"是不是"的问题,比如"订单是否允许改地址"。
-
选择组件:回答"选哪一个"的问题,比如"当前场景应该采用哪种处理方式"。
-
结果组件:负责组织最终输出,比如返回是否允许及原因。
组件也可以有不同实现方式。稳定且复杂的逻辑适合写成 Java 组件,例如需要访问本地服务、复用复杂算法的逻辑;频繁调整、表达式较简单的逻辑适合脚本组件,例如某几个字段之间的条件判断。
链路:把组件编排成完整业务决策
链路就是完整的规则流程。它把多个组件按顺序、条件和分支组织起来,形成一次完整的业务决策。
地址编辑规则的链路可以抽象成:
Plaintext
开始
-> 判断城市是否支持地址编辑
-> 判断订单状态是否允许改地址
-> 如果两个判断都通过,输出 allowEditAddress = true
-> 否则输出 allowEditAddress = false 及原因
结束
在运行时,业务服务只需要调用:
Plaintext
execute("address_edit_strategy", requestParams)
SDK 会根据链路定义执行对应组件,把中间结果放入上下文,最终返回结构化结果。
这几个抽象组合起来之后,规则配置就不再是"配置一段 JSON",而是形成了一套可维护的规则资产体系:空间解决隔离和权限,类别解决组织和查找,选项解决字段标准化,组件解决逻辑复用,链路解决完整决策编排。
当这些抽象和底层运行机制都具备之后,平台带来的变化就不只是"配置更方便",而是规则从开发、验证、上线到回滚的整个生命周期都被重新组织了一遍。
八、实践效果
规则可视化配置平台适合承接这类场景:
-
判断条件多,且经常变化的业务开关。
-
需要输出结构化结果的业务策略。
-
需要按城市、渠道、业务线、人群、订单状态等维度分支的规则。
-
需要发布审批、线上对比、灰度切流和快速回滚的高风险配置。
-
希望沉淀字段、组件和链路资产的复杂业务域。
效率提升
| 改造前 | 改造后 |
|---|---|
| 配置结构自由,人工维护容易出错 | 页面可视化编辑,字段、组件、链路结构化管理 |
| 回归场景困难,需要构造真实业务链路 | 页面测试用例可直接构造参数并执行规则 |
| 规则发布后责任人感知弱 | 审批、通知、发布卡片统一管控 |
| 规则散落在代码和配置中,复用困难 | 字段、组件、链路沉淀为规则资产 |
| 变更一次性全量生效风险高 | 双跑对比、灰度切流、快速回滚 |
这些效果并不是某个单一功能带来的,而是结构化表达、发布治理、本地执行和热更新机制共同作用的结果。
九、总结
规则可视化配置平台的价值,不只是把规则"搬到页面上配置",而是把复杂业务规则从散落的代码和配置中抽象出来,形成一套完整的表达、验证、发布、运行和治理机制。
它背后的关键设计可以概括为三点:
-
用字段、组件、链路把复杂业务规则结构化。
-
用配置中心和运行时 SDK 把规则安全地下发并在业务服务本地执行。
-
用测试用例、审批、双跑、灰度和快照回滚控制规则变更风险。
当业务规则越来越多、变化越来越频繁时,单纯依赖代码分支或普通配置中心都会逐渐难以维护。把规则平台化、资产化、治理化,才是长期可维护的方向。