后台开发里有一类需求反复出现:审批链路随条件变长变短、多分支并行再汇合、超阈值触发额外环节。硬编码 if-else 嵌套三层就失控,用状态机又嫌重。Solon Flow 给了一条中间路线——用 YAML 描述有向图,引擎自动驱动节点流转,50 行就能跑起一条完整的请假审批流。
审批流的本质是有向图
先看几个典型场景:
- 请假审批:1 天以内主管拍板,3 天以内加部门经理,超过 3 天还得副总签字——链路随天数动态拉长。
- 合同审批:金额超 10 万触发会签,超 50 万财务必须介入——阈值决定分支。
- 数据抓取:多线程并行爬多个源,全部完成后再汇总——并行再汇合。
这些需求的核心结构是一样的:节点 + 有向边 + 条件分支。硬写 Java 代码,每个分支一个 if,每个汇合一个计数器,代码量膨胀且难以复用。流程编排框架的价值在于:你只描述"图怎么画",引擎负责"节点怎么走"。
Solon Flow 的 YAML DSL
Solon Flow 是 Solon 框架的流程编排组件,核心思路是 YAML 定义流程图,Java 负责节点逻辑。一个流程定义文件大致结构如下:
flow:
id: leave_approval
nodes:
- id: submit # 员工提交
- id: manager_review # 主管审批
- id: dept_review # 部门经理审批
- id: vp_review # 副总审批
- id: end # 流程结束
chains:
- from: submit
to: manager_review
- from: manager_review
to: dept_review
when: days > 1 # 超过1天才走部门经理
- from: manager_review
to: end
when: days <= 1 # 1天以内直接结束
- from: dept_review
to: vp_review
when: days > 3 # 超过3天走副总
- from: dept_review
to: end
when: days <= 3 # 3天以内直接结束
- from: vp_review
to: end
这段 YAML 做了几件事:
- 声明节点——每个审批环节是一个 node,id 是唯一标识。
- 声明边和条件——
chains定义从哪个节点到哪个节点,when是路由条件,引擎根据上下文变量自动选择分支。 - 天然支持多分支——同一个
from可以有多个to,条件互斥时就是排他分支,条件不互斥时就是并行分支。
上面的 YAML 加上注释和空行约 50 行,已经覆盖了"不同天数走不同链路"的核心逻辑。
Java 端接入:节点逻辑与流程启动
YAML 只描述图,节点里干什么还得写 Java。Solon Flow 的节点组件用 @NodeComponent 注解声明:
// 主管审批节点
@NodeComponent("manager_review")
public class ManagerReviewNode implements NodeComponent {
@Override
public void execute(Context ctx) {
// ctx 包含流程上下文,可读取提交的请假天数、申请人等
int days = ctx.get("days", 0);
String applicant = ctx.get("applicant", "");
// 模拟审批逻辑:调用内部审批服务或直接写结果
boolean approved = approvalService.managerApprove(applicant, days);
ctx.set("manager_approved", approved);
if (!approved) {
// 审批拒绝,可以直接跳到 end 或走拒绝分支
ctx.set("result", "rejected_by_manager");
}
}
}
// 部门经理审批节点
@NodeComponent("dept_review")
public class DeptReviewNode implements NodeComponent {
@Override
public void execute(Context ctx) {
boolean approved = approvalService.deptApprove(
ctx.get("applicant", ""), ctx.get("days", 0));
ctx.set("dept_approved", approved);
if (!approved) {
ctx.set("result", "rejected_by_dept");
}
}
}
启动流程只需一行:
// 启动请假审批流程
FlowEngine engine = Solon.app().getBean(FlowEngine.class);
Context ctx = new Context();
ctx.set("applicant", "张三");
ctx.set("days", 2); // 请假2天,预期链路:submit -> manager -> dept -> end
engine.start("leave_approval", ctx);
引擎会根据 days 的值自动走 manager_review -> dept_review -> end,跳过 vp_review。请假 5 天时,链路自动拉长到副总节点。
并行与汇合:合同审批的会签场景
请假审批是串行+条件分支,合同审批则需要并行会签。YAML 里同一个 from 下多个 to 不加互斥条件,就是并行分支:
flow:
id: contract_approval
nodes:
- id: submit
- id: legal_review # 法务审核
- id: finance_review # 财务审核(金额>50万才参与)
- id: dept_sign # 部门会签
- id: merge # 汇合点
- id: end
chains:
- from: submit
to: legal_review
- from: submit
to: dept_sign
when: amount > 100000
- from: submit
to: finance_review
when: amount > 500000
- from: legal_review
to: merge
- from: dept_sign
to: merge
- from: finance_review
to: merge
- from: merge
to: end
merge 是汇合节点——引擎会等所有入边对应的节点完成后才推进。金额 80 万时,legal_review 和 dept_sign 并行跑完再汇合;金额 60 万时,三个节点并行,全部完成才往下走。
实践中的几个注意点
条件表达式要简单可测。 when 里的表达式(如 days > 1)由引擎内置解析器处理,支持基本比较和逻辑运算。复杂业务判断(比如"该员工是否在黑名单")不要塞进 when,放到节点逻辑里用 Java 写,YAML 只管路由。
拒绝路径要显式声明。 审批拒绝不是"流程自然结束",而是走一条明确的拒绝分支。建议在 YAML 里加一条 from: manager_review to: rejected when: manager_approved == false,拒绝节点里发通知、写日志,比隐式终止更可控。
流程上下文是线程安全的。 Context 对象随流程实例创建,节点间通过 ctx.set / ctx.get 传递数据。并行分支中不同节点写同一个 key 会冲突,建议用带节点前缀的 key(如 manager_approved、dept_approved)。
YAML 和 Java 分离带来可维护性。 改审批链路只改 YAML,不动 Java 代码;改审批逻辑只改 NodeComponent,不动流程图。这种分离在审批规则频繁变动的业务里尤其有价值。
什么时候该用,什么时候不该用
适合用 Solon Flow 的场景:
- ✅ 多步骤审批,链路随条件动态变化
- ✅ 需要并行+汇合的编排(会签、多源抓取汇总)
- ✅ 流程规则频繁变动,希望改配置不改代码
- ✅ 已在用 Solon 框架的项目,接入成本极低
不适合的场景:
- ❌ 只有两三个固定步骤,if-else 就够用,引入 DSL 反而增加理解成本
- ❌ 需要复杂子流程嵌套、动态增删节点——当前 DSL 的表达能力有限
- ❌ 非 Solon 生态项目——单独引入一个框架的流程组件,依赖链偏重
上手清单:
- 项目引入
solon-flow依赖 - 在
src/main/resources/flow/下写 YAML 流程定义 - 为每个节点写
@NodeComponent实现类 - 注入
FlowEngine,调用start启动流程 - 用
ctx传业务数据,用when控制路由
50 行 YAML 画完图,几个 Java 类填完逻辑,审批流就能跑起来。比起三层 if-else 嵌套,这张有向图一眼就能看出谁审批、什么条件跳过谁、拒绝走哪条路——这才是流程编排该有的样子。