业务可视化-让你的流程图"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。
可以编辑简单流程,如下

业务可视化-让你的流程图"Run"起来

还可以实现流程图和json之间的互转。

业务可视化-让你的流程图"Run"起来

业务可视化-让你的流程图"Run"起来

我们把这些节点的基本信息拿到,就可以得到一张图,然后通过程序遍历这张图的每个节点,即可达到运行流程图的效果。

接下来就是流程图的节点与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的配置文件,放在相同的目录下。
业务可视化-让你的流程图"Run"起来

业务可视化-让你的流程图"Run"起来

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贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。

业务可视化-让你的流程图"Run"起来

异常结束日志如下

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贴到下面的位置,查看看结果(绿色表示正常结束,红色表示异常结束,白色表示等待执行)。

业务可视化-让你的流程图"Run"起来

源码:https://github.com/nobuglady/nobuglady-network

感谢阅读。欢迎Star。

 

发表评论

评论已关闭。

相关文章