graph - 动态图生成与运行时变更
在企业级应用中,最头疼的需求莫过于“流程定制化”。不同的租户、不同的业务场景,往往需要在标准流程的基础上增加或减少某些步骤。如果为每个场景都预定义一个配置文件,系统将变得臃肿不堪。
本篇我们将解锁 Solon-Flow 的杀手锏——动态图编排,看看如何利用 GraphSpec.copy() 在运行时完成流程的“乾坤大挪移”。
1、动态性的核心价值
常见的流程引擎的图定义是“静态”的(从 JSON 或 XML 或 YAML 加载后便不再改变)。而 Solon-Flow 允许你在内存中动态构建、复制和修饰图定义。
- 千人千面:根据租户配置,动态决定是否插入“短信通知”节点。
- 运行时修正:在不停止服务的情况下,临时调整某个节点的审批逻辑。
- 热插拔插件:根据数据库中的插件列表,实时拼装业务链路。
2、核心 API:GraphSpec.copy()
GraphSpec.copy(graph) 是实现动态性的核心方法。它能将一个已有的 Graph 实例反转回 GraphSpec 状态,让你在保留原有逻辑的基础上进行增删改。
源码逻辑分析:
public static GraphSpec copy(Graph graph) {
// 内部通过 toJson 序列化再反序列化,确保得到一个完全独立的规格副本
return fromText(graph.toJson());
}
3、实战场景一:流程的“动态微调”
假设我们有一个标准的“请假流程”,但针对“高级VIP员工”,我们需要在最后动态增加一个“行政关怀”节点。
public Graph getDynamicFlow(boolean isVip) {
// 1. 获取标准流程图
Graph standardGraph = flowEngine.getGraph("leave_flow");
if (!isVip) {
return standardGraph;
}
// 2. 动态克隆并修饰
GraphSpec spec = GraphSpec.copy(standardGraph);
// 3. 在原有流程中“动刀”
// 假设原流程最后节点是 'end',我们在 'audit' 之后插入 'care' 节点
spec.getNode("audit").linkRemove("end").linkAdd("care");
spec.addActivity("care")
.title("行政关怀")
.task("@careProcessor")
.linkAdd("end");
// 4. 生成新图
return spec.create();
}
或者使用 Graph.copy (内部基于 GraphSpec.copy),更简化:
public Graph getDynamicFlow(boolean isVip) {
// 1. 获取标准流程图
Graph standardGraph = flowEngine.getGraph("leave_flow");
if (!isVip) {
return standardGraph;
}
// 2. 动态克隆并修饰
return Graph.copy(standardGraph, spec -> {
// 3. 在原有流程中“动刀”
// 假设原流程最后节点是 'end',我们在 'audit' 之后插入 'care' 节点
spec.getNode("audit").linkRemove("end").linkAdd("care");
spec.addActivity("care")
.title("行政关怀")
.task("@careProcessor")
.linkAdd("end");
});
}
4、实战场景二:根据数据库“插件列表”实时拼装
在复杂的 SaaS 系统中,流程往往由多个可选插件组成。我们可以通过代码逻辑,将离散的插件节点拼装成一条完整的线。
public Graph buildPluginFlow(List<PluginDef> plugins) {
GraphSpec spec = new GraphSpec("plugin_flow", "动态插件流");
NodeSpec lastNode = spec.addStart("start");
for (int i = 0; i < plugins.size(); i++) {
PluginDef p = plugins.get(i);
String nodeId = "p_" + i;
// 动态添加节点
spec.addActivity(nodeId)
.title(p.getTitle())
.task(p.getTaskBeanName());
// 自动连接上一个节点
lastNode.linkAdd(nodeId);
lastNode = spec.getNode(nodeId);
}
lastNode.linkAdd("end");
spec.addEnd("end");
return spec.create();
}
或者使用 Graph.create (内部基于 GraphSpec),更简化:
public Graph buildPluginFlow(List<PluginDef> plugins) {
return Graph.create("plugin_flow", "动态插件流", spec -> {
NodeSpec lastNode = spec.addStart("start");
for (int i = 0; i < plugins.size(); i++) {
PluginDef p = plugins.get(i);
String nodeId = "p_" + i;
// 动态添加节点
spec.addActivity(nodeId)
.title(p.getTitle())
.task(p.getTaskBeanName());
// 自动连接上一个节点
lastNode.linkAdd(nodeId);
lastNode = spec.getNode(nodeId);
}
lastNode.linkAdd("end");
spec.addEnd("end");
});
}
5. 性能与安全建议
虽然动态生成非常强大,但在生产环境中使用时需注意以下几点:
对象缓存:虽然 GraphSpec.create() 很快,但如果在高并发接口中频繁 copy 和 create,会产生大量碎片对象。建议根据业务 Key(如 tenantId + flowId)将生成的 Graph 对象缓存在本地或 FlowEngine 中。
拓扑校验:动态拼装图时,务必保证流程的闭环。Solon-Flow 会在执行时检查路径完整性,但在编排阶段建议自行校验 Start 和 End 是否存在。
线程安全:GraphSpec 是非线程安全的,建议在局部变量中完成构建后再发布为 Graph 实例。
结语
Solon-Flow 的动态性让开发者从“写死配置”的苦力中解脱出来。通过 GraphSpec.copy() 和编排 API,你可以像操纵普通 Java 对象一样操纵业务逻辑图。
在下一篇 《影子分身 —— 节点的拦截、监听与 AOP》 中,我们将研究如何在不改动图结构的情况下,为这些节点挂载统一的“监控”和“治理”能力。