当业务复杂度爆炸,你的代码还能撑多久?
做过复杂业务的 Java 开发者,大概都经历过这样的噩梦:一个订单处理方法,从最初的 50 行,经过三轮需求迭代,膨胀到 500 行。里面嵌套着六层 if-else,夹杂着硬编码的分支逻辑,改一处怕崩一片。新人来了看不懂,老人走了不敢改。更糟糕的是,产品经理隔三差五就跑过来说:"这个审批流程要加一个总监审批环节"、"满减规则从三档改成五档"、"大客户走特殊通道"......每一次变更都是一次代码外科手术。
流程编排就是为解决这个问题而生的。把散落在代码各处的业务逻辑,抽象为一个个独立的"节点",然后用声明式的方式定义它们的执行顺序和分支条件。这样既解耦了代码,又让业务规则一目了然,更重要的是------规则的变更不再需要修改 Java 代码、走完整的发版流程。
今天要介绍的是------Solon Flow。它不仅覆盖了 "规则编排" 和 "任务流水线",还原生支持 "AI Agent 编排",而且不绑定任何特定框架,Spring Boot 项目一样能用。
一、Solon Flow 是什么?
Solon Flow 是基于 Solon 框架 构建的通用流程编排引擎,采用"偏平式编排"设计,所有节点平铺在同一个层级,通过 link(连接线)描述节点之间的流转关系。它不依赖树形嵌套、不依赖 EL 表达式,而是用最直觉的图结构来表达业务流程。
设计理念
1. 偏平式编排
Solon Flow 把所有节点平铺,用 link 的 when 条件控制流向。这种风格更接近流程图的直觉表达,也更容易被非开发角色理解。
yaml
# 偏平式编排:节点平铺,link 描述连接关系
id: order_flow
layout:
- id: start
type: start
link: check_order
- id: check_order
type: activity
task: @checkOrderTask
link:
- nextId: vip_route
when: "amount >= 1000"
- nextId: normal_route
- id: ...
2. 元数据无限扩展
每个节点都可以附加任意 meta 属性。这意味着你可以在不修改引擎代码的前提下,为节点注入角色信息、表单定义、超时策略、重试次数等业务元数据,实现高度的定制化。比如审批流中,你可以在 meta 里定义每个审批节点对应的角色(role: tl)和表单(form: approval_form),运行时由驱动器读取并执行对应的权限检查。这种设计让 YAML 文件不仅是执行编排的定义,同时也是一份业务级的"配置文档"。
3. 驱动可定制
Solon Flow 引入了类似 JDBC 驱动的机制------引擎本身只定义执行骨架,具体的任务调度、条件评估等行为通过 driver(驱动器)接口注入。这意味着同一套编排定义,换一个驱动器就能变成不同的执行范式。默认驱动器使用 Solon 的 IoC 容器来解析 TaskComponent,但你完全可以提供一个纯 Spring 驱动器,让它通过 Spring 的 ApplicationContext 来获取 Bean。这种可插拔的驱动设计,是 Solon Flow 实现"框架无关"的关键技术基础。
4. 可中断可恢复
这是 Solon Flow 最独特的能力之一。通过 context.stop() 中断流程,用 context.toJson() 将执行快照序列化持久化,之后通过 FlowContext.fromJson() 恢复上下文,引擎会自动从中断节点继续执行。注意------不是从起点重新执行,而是精确地从上次中断的节点继续。这个能力对审批流、长流程、AI Agent 的持久化至关重要。你可以把快照存到数据库、Redis、甚至消息队列中,根据业务需要灵活选择持久化策略。
二、七种节点类型全解析
Solon Flow 定义了 7 种节点类型,覆盖了流程编排中最常见的执行模式。
| 节点类型 | 标识 | 说明 | 典型场景 |
|---|---|---|---|
| 开始节点 | start |
图的唯一入口,无任务执行 | 流程起点 |
| 活动节点 | activity |
执行具体业务逻辑的默认节点 | 计算任务、服务调用 |
| 包容网关 | inclusive |
多选分支,满足条件的都执行,汇聚时等待所有已激活分支 | 多条件触发 |
| 排它网关 | exclusive |
单选分支,按优先级选择唯一一条路径 | 条件路由 |
| 并行网关 | parallel |
全选分支,所有连接都执行,汇聚时等待全部完成 | 并行计算 |
| 循环网关 | loop |
遍历集合(或区间),每个元素执行一次子流程 | 批量处理 |
| 结束节点 | end |
图的终点 | 流程结束 |
下面逐个看 YAML 配置示例。
start --- 开始节点
yaml
- id: s1
type: start
title: 流程开始
link: n1
start 节点是图的唯一入口,不执行任务,只负责触发后续节点。每个图有且只有一个 start 节点。从 v3.8.4 开始,start 节点支持自由流出连接(与 activity 类似),可以连接多个下游节点。
activity --- 活动节点(默认)
yaml
- id: n1
type: activity
title: 数据校验
task: @validateTask
meta:
role: admin
link: n2
activity 是最常用的节点类型,执行具体的业务逻辑(通过 task 绑定 TaskComponent),还可以通过 meta 携带任意扩展数据。
exclusive --- 排它网关(单选)
yaml
- id: g1
type: exclusive
title: 金额分级
link:
- nextId: n2
title: "3天以下"
- nextId: n3
title: "3天以上"
when: "day >= 3"
排它网关按连接声明的优先级依次评估 when 条件,选择第一个满足条件 的路径执行。没有 when 条件的连接视为默认路由(兜底)。
inclusive --- 包容网关(多选)
yaml
- id: g1
type: inclusive
title: 权益发放
link:
- nextId: coupon_task
when: "orderAmount >= 100"
- nextId: point_task
when: "orderAmount >= 50"
- nextId: gift_task
when: "orderAmount >= 500"
包容网关会评估所有流出连接的条件,所有满足条件的分支都会执行,汇聚时等待所有已激活的分支完成。类似于"多选多"。
parallel --- 并行网关(全选)
yaml
- id: p1
type: parallel #流出
title: 并行查询
link:
- nextId: query_user
- nextId: query_order
- nextId: query_inventory
# ... 三个任务各自完成后汇聚到 p2
- id: p2
type: parallel #流入,网关汇聚
title: 汇聚结果
link: merge_result
并行网关的流出连接全部执行(多线程并行,或单线程执行),汇聚端等待所有流入连接到齐后才继续。适合同时调用多个独立服务的场景。
loop --- 循环网关(遍历)
yaml
- id: loop1
type: loop #流出
title: 遍历订单项
meta:
$for: "order" # 指定要遍历接收变量名
$in: "orderList" # 指定要遍历的集合变量名
link: process_item
- id: process_item
type: activity
task: @processItemTask
- type: loop #流入,网关汇聚
循环网关会对集合中的每个元素执行一次子流程,通过 $for、$in 元数据指定遍历的集合变量名。
end --- 结束节点
yaml
- id: e1
type: end
title: 流程结束
end 节点标记流程的终点。一个图可以有多个 end 节点(对应不同的结束路径),但每个 end 节点不能有流出连接。流程执行到 end 节点时,引擎会记录完成状态,context.lastRecord().isEnd() 返回 true。
三、五大实战场景
掌握了七种节点类型后,我们来看五个典型的实战场景,从简单到复杂逐步深入。
场景 A:业务规则引擎 --- 订单金额分级积分计算
先看一个经典的需求:根据订单金额计算积分,规则经常变化,不想每次改代码重新发版。
传统 if-else 写法:
java
public int calculatePoints(Order order) {
if (order.getAmount() >= 10000) {
return (int)(order.getAmount() * 0.15);
} else if (order.getAmount() >= 5000) {
return (int)(order.getAmount() * 0.10);
} else if (order.getAmount() >= 1000) {
return (int)(order.getAmount() * 0.05);
} else {
return (int)(order.getAmount() * 0.01);
}
}
每次规则变化都要改代码、测试、发版。现在用 Solon Flow 改造。
YAML 编排定义:
yaml
id: points_calc
nodes:
- id: start
type: start
link: g1
- id: g1
type: exclusive
title: 积分分级
link:
- nextId: diamond
when: "amount >= 10000"
- nextId: gold
when: "amount >= 5000"
- nextId: silver
when: "amount >= 1000"
- nextId: normal
- id: diamond
type: activity
title: 钻石级积分
meta:
rate: "0.15"
task: @ratePointsTask
link: end
- id: gold
type: activity
title: 黄金级积分
meta:
rate: "0.10"
task: @ratePointsTask
link: end
- id: silver
type: activity
title: 白银级积分
meta:
rate: "0.05"
task: @ratePointsTask
link: end
- id: normal
type: activity
title: 普通积分
meta:
rate: "0.01"
task: @ratePointsTask
link: end
- id: end
type: end
Java 调用代码:
java
// 引擎初始化(一行代码搞定)
FlowEngine engine = FlowEngine.newInstance();
Graph graph = Graph.fromUri("points_calc.yml");
// 执行
FlowContext context = FlowContext.of();
context.put("amount", 6800);
engine.eval(graph, context);
int points = context.getAs("points");
核心 API 就三个:FlowEngine.newInstance() 创建引擎、Graph.fromUri() 加载编排定义、engine.eval() 执行。学习成本极低,五分钟上手。
TaskComponent 实现(所有分级共用一个):
java
@Component("ratePointsTask")
public class RatePointsTask implements TaskComponent {
@Override
public void run(FlowContext ctx, Node node) throws Throwable {
double amount = ctx.getAs("amount");
double rate = Double.parseDouble(node.getMeta("rate"));
ctx.put("points", (int)(amount * rate));
}
}
注意看:四个分级节点共享同一个 ratePointsTask,通过 node.getMeta("rate") 读取 YAML 中定义的 rate 元数据。这意味着新增一个分级只需要在 YAML 中加一个节点和对应的 rate 值,Java 代码完全不用动。
优势对比: 传统 if-else 方式,新增分级要改 Java 代码、走测试流程、发版上线;Solon Flow 方式只需修改 YAML 文件中的阈值和费率,运维同学甚至可以在线上直接修改配置。当规则从四档变成六档、十档时,收益会更加明显。
场景 B:任务编排 --- 数据抓取流水线
一个典型的 ETL 场景:抓取数据 → 清洗 → 存储 → 通知。每个环节都是独立的组件,用 activity 节点串联。
YAML 定义:
yaml
id: etl_pipeline
nodes:
- id: start
type: start
link: fetch
- id: fetch
type: activity
title: "数据抓取"
task: @fetchDataTask
link: clean
- id: clean
type: activity
title: "数据清洗"
task: @cleanDataTask
link: store
- id: store
type: activity
title: "数据存储"
task: @storeDataTask
link: notify
- id: notify
type: activity
title: "完成通知"
task: @notifyTask
link: end
- id: end
type: end
TaskComponent 实现:
java
@Component("fetchDataTask")
public class FetchDataTask implements TaskComponent {
@Override
public void run(FlowContext ctx, Node node) throws Throwable {
// 调用外部 API 抓取数据
List<RawData> data = apiClient.fetch(ctx.getAs("source"));
ctx.put("rawData", data);
}
}
@Component("cleanDataTask")
public class CleanDataTask implements TaskComponent {
@Override
public void run(FlowContext ctx, Node node) throws Throwable {
List<RawData> raw = ctx.getAs("rawData");
List<CleanData> cleaned = raw.stream()
.filter(d -> d.isValid())
.map(this::transform)
.collect(Collectors.toList());
ctx.put("cleanData", cleaned);
}
}
流水线的每一步都是一个独立的 TaskComponent,通过 FlowContext 传递数据。这种设计带来了几个好处:
- 每个 TaskComponent 可以独立测试------不依赖 FlowEngine,直接构造 FlowContext 传入即可单元测试
- 组件复用------同一个 TaskComponent 可以在不同的图中被引用
- 插入/删除步骤零侵入------如果需要在"清洗"和"存储"之间插入一个"去重"步骤,只需要在 YAML 里加一个节点和一条 link,Java 代码完全不需要改动
- 执行顺序可视化------YAML 文件本身就是一份可读的流程文档,产品经理和测试同学都能看懂
场景 C:并行计算 --- 多任务并行执行
当流水线中存在多个互不依赖的任务时,可以用 parallel 网关让它们并行执行,显著缩短总耗时。
YAML 定义:
yaml
id: parallel_query
nodes:
- id: start
type: start
link: fork
- id: fork
type: parallel
title: "并行查询"
link:
- nextId: query_user
- nextId: query_order
- nextId: query_risk
- id: query_user
type: activity
title: "查询用户信息"
task: @queryUserTask
link: join
- id: query_order
type: activity
title: "查询订单信息"
task: @queryOrderTask
link: join
- id: query_risk
type: activity
title: "风控评估"
task: @queryRiskTask
link: join
- id: join
type: parallel
title: "汇聚结果"
link: aggregate
- id: aggregate
type: activity
title: "汇总决策"
task: @aggregateTask
link: end
- id: end
type: end
三个查询任务并行执行,join 节点(parallel 类型)会等待三个分支全部完成后,再流转到 aggregate 节点进行汇总。原来串行 3 秒的任务(1s + 0.8s + 1.2s),并行后总耗时取决于最慢的那个分支,可能只需 1.2 秒。
使用 parallel 网关需要注意几个要点:
- parallel 网关必须成对使用------一个 fork(分叉)、一个 join(汇聚)
- join 端会等待所有流入连接到齐,任何一个分支异常都会导致汇聚失败
- 并行执行的线程策略由 driver 决定,默认使用线程池并行执行
- 各分支之间通过各自的
FlowContext隔离,互不干扰
场景 D:可中断与恢复的审批流
审批流是流程编排的经典场景。一个订单需要经过主管和总监两级审批,但审批人不是实时在线的,流程需要在中途"暂停",等审批完成后再"继续"。
YAML 定义:
yaml
id: order_approval
nodes:
- id: start
type: start
link: submit
- id: submit
type: activity
title: 提交订单
task: @submitTask
link: tl_review
- id: tl_review
type: activity
title: 主管审批
task: @tlReviewTask
link: vp_review
- id: vp_review
type: activity
title: 总监审批
task: @vpReviewTask
link: end
- id: end
type: end
审批 Task 的中断逻辑:
java
@Component("tlReviewTask")
public class TlReviewTask implements TaskComponent {
@Override
public void run(FlowContext ctx, Node node) throws Throwable {
ApprovalState state = ctx.getAs("approvalState");
if (!state.isTlApproved()) {
// 主管尚未审批,中断流程
System.out.println("等待主管审批...");
ctx.stop(); // 关键:停止执行
} else {
System.out.println("主管已通过,流转到总监审批。");
}
}
}
中断后的持久化与恢复:
java
FlowEngine engine = FlowEngine.newInstance();
Graph graph = Graph.fromUri("order_approval.yml");
FlowContext context = FlowContext.of("order-001");
// 第一次执行:提交后主管审批环节被中断
context.put("approvalState", new ApprovalState());
engine.eval(graph, context);
// context.isStopped() == true,流程被暂停了
System.out.println("当前停在节点: " + context.lastNodeId()); // 输出: tl_review
// 持久化到数据库
String snapshot = context.toJson();
approvalRepository.saveSnapshot("order-001", snapshot);
// ---- 几天后,主管完成了审批 ----
// 从数据库恢复
String json = approvalRepository.getSnapshot("order-001");
FlowContext restored = FlowContext.fromJson(json);
restored.put("approvalState", new ApprovalState(true, false)); // 主管已批
// 引擎自动从 tl_review 节点继续执行
engine.eval(graph, restored);
这就是 Solon Flow 的"中断-恢复"机制:stop() → toJson() 持久化 → fromJson() 恢复 → eval() 继续。整个过程中,业务数据的完整性由快照保证,引擎自动定位到中断节点。
场景 E:AI 智能体编排
这是 Solon Flow 最差异化的能力,也是其他编排框架几乎没有覆盖的领域。
Solon 的 AI 生态构建在 Flow 之上,提供了三个层次的智能体能力:
| 插件 | 智能体模式 | 说明 |
|---|---|---|
solon-ai-flow |
AiFlow 工作流模式 | 偏软编排,用 YAML 编排 AI 工作流 |
solon-ai-agent |
ReActAgent 自我反省模式 | 单兵作战,思考+行动循环 |
solon-ai-agent |
TeamAgent 团队协作模式 | 多智能体系统,分工协作 |
ReActAgent:思考-行动-观察的闭环
ReActAgent 的核心是"思考(Thought)→ 行动(Action)→ 观察(Observation)"的推理循环。它底层就是一个由 Solon Flow 构建的计算图:
Start → [Plan(可选)] → [Reason(决策)] ↔ [Action(执行工具)] → End
java
// 构建一个技术支持专家
ReActAgent techAgent = ReActAgent.of(chatModel)
.name("tech_support")
.role("技术支持专家")
.instruction("负责处理客户技术问题,可查询数据库和排查日志")
.defaultToolAdd(dbQueryTool) // 注入数据库查询工具
.defaultToolAdd(logSearchTool) // 注入日志搜索工具
.maxSteps(12) // 最多推理 12 步
.build();
// 执行任务
AssistantMessage result = techAgent
.prompt("查询最近1小时订单号为 ORD-20260517-001 的错误日志")
.call();
TeamAgent:多智能体团队协作
当任务需要多个不同角色的专家协作时,TeamAgent 登场。它本身不执行任务,而是作为"总指挥",通过 TeamProtocol(协作协议)将多个 Agent 编织成协同作战单元。
java
TeamAgent tripAgent = TeamAgent.of(chatModel)
.name("trip_manager")
.role("旅行规划团队")
.agentAdd(ReActAgent.of(chatModel)
.name("searcher")
.role("天气搜索员")
.instruction("负责提供实时气候数据")
.defaultToolAdd(weatherTool)
.build())
.agentAdd(ReActAgent.of(chatModel)
.name("planner")
.role("行程规划师")
.instruction("根据天气数据规划行程")
.build())
.protocol(TeamProtocols.SEQUENTIAL) // 顺序协作协议
.build();
// 执行团队任务
AssistantMessage result = tripAgent
.prompt("帮我规划上海周末行程")
.call();
AiFlow:工作流智能体(YAML 编排 AI)
如果你更喜欢声明式地编排 AI 工作流,solon-ai-flow 提供了类似 Dify 的工作流模式,但它是纯开发框架而非低代码平台:
xml
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-ai-flow</artifactId>
</dependency>
AiFlow 内置了多种预置组件:输入组件(接收用户消息)、模型组件(调用 LLM)、RAG 组件(检索增强生成)、输出组件(返回结果)。你可以用 YAML 定义这些组件之间的流转关系,像搭积木一样拼装出复杂的 AI 工作流。而且由于底层就是 Solon Flow,你可以随时用自定义的 TaskComponent 替换或扩展任何预置组件------这是它区别于 Dify 等封闭式低代码平台的核心优势。
关键洞察: Solon Flow 不是为了 AI 而生,但它的图驱动执行模型天然适合 AI Agent 的编排需求。ReActAgent 的推理循环(Reason ↔ Action)、TeamAgent 的多角色协作(路由决策 + 专家分发),底层都是 Solon Flow 的 Graph 在驱动。这意味着------你在 Flow 上积累的编排经验、调试手段和可视化工具,可以无缝复用到 AI 场景。当你的业务系统未来需要引入 AI 能力时,不需要再学习一套全新的编排框架。
另一个值得注意的点:TeamAgent 支持持久化与恢复。你可以将多智能体协作的中间状态序列化为 JSON 快照存入数据库,系统重启后从中断点继续执行。这对于耗时较长的 AI 任务(如多轮分析、大规模报告生成)非常关键。
四、框架无关:六种框架都能用
一个常见的疑虑:"Solon Flow 是不是必须用 Solon 框架?"
答案是不需要。
Solon Flow 可以作为纯 JAR 包引入,不需要 Solon 容器。官方提供了完整的嵌入式示例,覆盖主流 Java 框架:
| 嵌入框架 | 示例模块 |
|---|---|
| Spring Boot 2 | solon-flow-in-springboot2 |
| Spring Boot 3 | solon-flow-in-springboot3 |
| Spring Boot 4 | solon-flow-in-springboot4 |
| Spring MVC | solon-flow-in-springmvc |
| jFinal | solon-flow-in-jfinal |
| Vert.X | solon-flow-in-vertx |
| Quarkus | solon-flow-in-quarkus |
| Micronaut | solon-flow-in-micronaut |
示例仓库地址:
- GitHub: https://github.com/solonlab/solon-flow-embedded-examples
- Gitee: https://gitee.com/solonlab/solon-flow-embedded-examples
Maven 依赖(就一个 JAR):
xml
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-flow</artifactId>
<version>3.10.7</version>
</dependency>
在 Spring Boot 项目中,只需要引入这个依赖,然后通过 Java API 方式使用 FlowEngine,不需要任何 Solon 相关的配置。核心 API 非常简洁:
java
// 1. 创建引擎
FlowEngine engine = FlowEngine.newInstance();
// 2. 从 classpath 加载 YAML
Graph graph = Graph.fromUri("my_flow.yml");
// 3. 构建上下文并执行
FlowContext context = FlowContext.of();
context.put("key", "value");
engine.eval(graph, context);
// 4. 读取结果
Object result = context.getAs("result");
这就是全部的 API 了。没有 XML 配置,没有 Spring Boot Starter 要引入,没有自动装配要理解。FlowEngine 是无状态的,线程安全,可以全局复用一个实例。Graph 加载后也是不可变的,可以缓存。只有 FlowContext 是每次执行需要新建的。