业务流程可视化-让你的流程图"Run"起来
前言
最近在研究业务可视化的问题,在日常的工作中,流程图和代码往往是分开管理的。
一个被维护多次的系统,到最后流程图和代码是否匹配这个都很难说。
于是一直有一个想法,让程序直接读流程图,根据流程图的配置来决定程序运行的顺序。
一转眼三年过去了,目前这个想法已经逐步落地实现变成代码。
问题
对于简单的流程
a -> b -> c
可以很容易用代码来实现
// 执行a a(); // 执行b b(); // 执行c c();
对于并行的流程
a -> b a -> c
这个就要多线程框架来实现
// 执行a a(); // a结束后执行b new Thread(b).start(); // a结束后执行c new Thread(c).start();
对于分支合并的流程
a -> b a -> c b -> d c -> d
程序会变得更加复杂
// 执行a a(); // a结束后执行b new Thread(b).start(); // a结束后执行c new Thread(c).start(); // 等待b,c结束 waitComplete(b,c); // 执行d d();
这个是最常用的业务流程,在实际写程序的时候,一般会避开多线程框架,往往被简单写成:
a(); b(); c(); d();
去除了Fork-Join的麻烦,也没有改变业务执行顺序,但和流程图稍有出入。
于是想到能不能有个框架来控制a,b,c,d的运行顺序呢?
也就我们只需要编写a,b,c,d的单体,执行顺序变成可配置。
调查
于是想到各种工作流框架和job执行框架可以满足这个需求,但是太重了。
为了简单的需求,引入庞大的工作流或者job执行引擎,无疑是每个项目都不能接受的。
于是,决定手写一个轻量的,即可以控制程序执行流程,又可以通过图形界面编辑程序流程的框架。
实现
首先要有一个绘制流程图的界面。并且能够将流程图转化为json格式。
这里我选择了Vis.js的network。
可以编辑简单流程,如下
还可以实现流程图和json之间的互转。
我们把这些节点的基本信息拿到,就可以得到一张图,然后通过程序遍历这张图的每个节点,即可达到运行流程图的效果。
接下来就是流程图的节点与Java的方法绑定了。
我做了一个Annotation来绑定流程图节点,
public @interface Node { String id() default "" ; String label() default "" ; }
节点得到运行开始事件后,拿到要运行的节点ID和名称,查找对应的类的Annotation对应的方法,如找到则运行该方法。
public int execute(String flowId, String nodeId, String historyId, HistoryNodeEntity nodeEntity) throws Exception{ String nodeName = nodeEntity.getNodeName(); System.out.println( "execute:" + nodeId); System.out.println( "node name:" + nodeEntity.getNodeName()); Method methods[] = this .getClass().getMethods(); if (methods != null ) { for (Method method:methods) { Node node = method.getAnnotation(Node. class ); if (node != null ) { if (node.id().equals(nodeId) || node.label().equals(nodeName)) { try { method.invoke( this ); return 0 ; } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); throw e; } } } } } return 0 ; }
使用方法
我们需要做一个继承自FlowRunner的类,里面的方法和flow的节点绑定,和一个flow的配置文件,放在相同的目录下。
MyFlow1.java
public class MyFlow1 extends FlowRunner { @Node (label= "a" ) public void process_a() { System.out.println( "processing a" ); } @Node (label= "b" ) public void process_b() { System.out.println( "processing b" ); } @Node (label= "c" ) public void process_c() { System.out.println( "processing c" ); } @Node (label= "d" ) public void process_c() { System.out.println( "processing d" ); } }
MyFlow1.json
{ "flowId" : "123" , "nodes" : [ { "id" : "1" , "label" : "start" }, { "id" : "2" , "label" : "a" }, { "id" : "0b5ba9df-b6c7-4752-94e2-debb6104015c" , "label" : "b" }, { "id" : "29bc32c7-acd8-4893-9410-e9895da38b2e" , "label" : "c" } ], "edges" : [ { "id" : "1" , "from" : "1" , "to" : "2" , "arrows" : "to" }, { "id" : "078ffa82-5eff-4d33-974b-53890f2c9a18" , "from" : "1" , "to" : "0b5ba9df-b6c7-4752-94e2-debb6104015c" , "arrows" : "to" }, { "id" : "90663193-7077-4aca-9011-55bc8745403f" , "from" : "2" , "to" : "29bc32c7-acd8-4893-9410-e9895da38b2e" , "arrows" : "to" }, { "id" : "a6882e25-c07a-4abd-907e-e269d4eda0ec" , "from" : "0b5ba9df-b6c7-4752-94e2-debb6104015c" , "to" : "29bc32c7-acd8-4893-9410-e9895da38b2e" , "arrows" : "to" } ] }
然后通过下面的代码来启动流程。
MyFlow1 myFlow1 = new MyFlow1(); myFlow1.startFlow();
系统关闭时,通过下面的代码关闭流程管理器
FlowStarter.shutdown();
运行
正常结束日志如下
Ready queue thread started.
Complete queue thread started.
json:
{"flowId":"123","nodes":[{"id":"1","label":"a"},{"id":"2","label":"b"},{"id":"0b5ba9df-b6c7-4752-94e2-debb6104015c","label":"c"},{"id":"29bc32c7-acd8-4893-9410-e9895da38b2e","label":"d"}],"edges":[{"id":"1","from":"1","to":"2","arrows":"to"},{"id":"078ffa82-5eff-4d33-974b-53890f2c9a18","from":"1","to":"0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows":"to"},{"id":"90663193-7077-4aca-9011-55bc8745403f","from":"2","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"},{"id":"a6882e25-c07a-4abd-907e-e269d4eda0ec","from":"0b5ba9df-b6c7-4752-94e2-debb6104015c","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"}]}
execute:1
node name:a
processing a
execute:2
node name:b
processing b
execute:0b5ba9df-b6c7-4752-94e2-debb6104015c
node name:c
processing c
execute:29bc32c7-acd8-4893-9410-e9895da38b2e
node name:d
processing d
Complete success.
json:
{"nodes":[{"id": "1","label": "a" ,"color": "#36AE7C"},{"id": "2","label": "b" ,"color": "#36AE7C"},{"id": "0b5ba9df-b6c7-4752-94e2-debb6104015c","label": "c" ,"color": "#36AE7C"},{"id": "29bc32c7-acd8-4893-9410-e9895da38b2e","label": "d" ,"color": "#36AE7C"}],"edges":[{"id": "1","from": "1","to": "2","arrows": "to"},{"id": "078ffa82-5eff-4d33-974b-53890f2c9a18","from": "1","to": "0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows": "to"},{"id": "90663193-7077-4aca-9011-55bc8745403f","from": "2","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"},{"id": "a6882e25-c07a-4abd-907e-e269d4eda0ec","from": "0b5ba9df-b6c7-4752-94e2-debb6104015c","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"}]}
流程执行结束后,会输出执行结果和运行后的流程图状态。
可以直接将json贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。
异常结束日志如下
Ready queue thread started.
Complete queue thread started.
json:
{"flowId":"123","nodes":[{"id":"1","label":"a"},{"id":"2","label":"b"},{"id":"0b5ba9df-b6c7-4752-94e2-debb6104015c","label":"c"},{"id":"29bc32c7-acd8-4893-9410-e9895da38b2e","label":"d"}],"edges":[{"id":"1","from":"1","to":"2","arrows":"to"},{"id":"078ffa82-5eff-4d33-974b-53890f2c9a18","from":"1","to":"0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows":"to"},{"id":"90663193-7077-4aca-9011-55bc8745403f","from":"2","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"},{"id":"a6882e25-c07a-4abd-907e-e269d4eda0ec","from":"0b5ba9df-b6c7-4752-94e2-debb6104015c","to":"29bc32c7-acd8-4893-9410-e9895da38b2e","arrows":"to"}]}
execute:1
node name:a
processing a
execute:2
node name:b
processing b
execute:0b5ba9df-b6c7-4752-94e2-debb6104015c
node name:c
processing c
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.github.nobuglady.network.fw.FlowRunner.execute(FlowRunner.java:49)
at io.github.nobuglady.network.fw.executor.NodeRunner.run(NodeRunner.java:93)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: test
at io.github.nobuglady.network.MyFlow1.process_b(MyFlow1.java:16)
... 11 more
java.lang.reflect.InvocationTargetException
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at io.github.nobuglady.network.fw.FlowRunner.execute(FlowRunner.java:49)
at io.github.nobuglady.network.fw.executor.NodeRunner.run(NodeRunner.java:93)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: test
at io.github.nobuglady.network.MyFlow1.process_b(MyFlow1.java:16)
... 11 more
Complete error.
json:
{"nodes":[{"id": "1","label": "a" ,"color": "#36AE7C"},{"id": "2","label": "b" ,"color": "#EB5353"},{"id": "0b5ba9df-b6c7-4752-94e2-debb6104015c","label": "c" ,"color": "#36AE7C"},{"id": "29bc32c7-acd8-4893-9410-e9895da38b2e","label": "d" ,"color": "#E8F9FD"}],"edges":[{"id": "1","from": "1","to": "2","arrows": "to"},{"id": "078ffa82-5eff-4d33-974b-53890f2c9a18","from": "1","to": "0b5ba9df-b6c7-4752-94e2-debb6104015c","arrows": "to"},{"id": "90663193-7077-4aca-9011-55bc8745403f","from": "2","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"},{"id": "a6882e25-c07a-4abd-907e-e269d4eda0ec","from": "0b5ba9df-b6c7-4752-94e2-debb6104015c","to": "29bc32c7-acd8-4893-9410-e9895da38b2e","arrows": "to"}]}
流程执行结束后,会输出执行结果和运行后的流程图状态。
可以直接将json贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。
源码:https://github.com/nobuglady/ladybugflow
感谢阅读。欢迎Star。