写在前面
要想自研工作流,先得要考虑好整体的模型再进行设计。在此不再介绍概念性问题,如什么是任务节点之类的。重点介绍如何抽象设计模型,并最终落地,希望能给大家一些启示,弥补市面上这类文章的缺失。阅读本文钱也可以先查看专栏中之前的文章进行简单了解 # 一个系列搞懂自研工作流(一)流程定义,了解流程定义如何抽象
模型介绍
简单的自研工作流,需要明白这么几个问题。
- 如何进行流程定义描述?
通过json/xml的方式描述对象关系,后续会有实例,一看便知。
- 如何将流程定义转成对象?
将json进行对象实例化,这个过程就有点像spring通过xml配置创建bean,只是过程更加简单。
- 如何存放流程对象?
像spring管理bean一样,我们也将解析完的流程对象放在类似于容器(执行对象)中进行管理,在流程执行环节中需要的时候拿出来。
流程定义解析
描述
{"id":"process_askForLeave","templateName":"请假流程","nodes":[{"id":"start","name":"开始","type":"start"},{"id":"leader_audit","name":"主管审批","type":"task","candidate":{"userIds":["11","U5B785B5AA1005919840278298192656"]}},{"id":"manager_audit","name":"经理审批","type":"task","candidate":{"userIds":["11","U5B785B5AA1005919840278298192656"]}},{"id":"end","name":"结束","type":"end"}],"lines":[{"to":"leader_audit","id":"start-leader_audit","from":"start","name":"发起流程"},{"to":"manager_audit","id":"leader_audit_manager_audit","from":"leader_audit","name":"主管审批通过"},{"to":"end","id":"askForLeaveApplyResult_end","from":"manager_audit","name":"经理审批通过"}]}
掘金中不知道如何设置json格式方便观看,大家将就看下扁平化的json(如果有大神知道的麻烦留言告知一下)。以上的json描述的就是这样一个流程定义。
主要的就是基本元素:id、templateName、nodes(节点list)、lines(连线list)。
流程定义解析
下面是定义的流程模型对象,用于存放解析后的数据,
public class ProcessModel {
/**
* 模板id
*/
private Long templateId;
/**
* 开始节点
*/
private NodeModel startModel;
/**
* 节点列表
*/
private List<NodeModel> nodes = new ArrayList<>();
/**
* 节点map列表
*/
private Map<String, NodeModel> nodeMap;
}
- startModel 开始节点,用于流程启动时快速找到启动的节点来执行。
- nodes 解析后的所有节点list,用list存储记录偏平化的节点数据
- nodeMap 解析后的所有节点map,方便后续用节点id直接获取对应的节点对象。
没有设计
lines记录所有连线对象的list,因为到目前为止没有需求需要直接获取连线对象,在节点node中有记录进出线对象list,可供操作。后续如果需要可以在解析的时候进行设计。
解析层代码
整体思路
- 取nodes先new出节点的基本属性
- 取出lines,new对象并装配NodeModel的source和target
- 然后将node节点中的进出线line对象列表注入
- 最后实例化ProcessModel对象结束。
话不多说上解析代码
public static ProcessModel parse(String json) {
ProcessTemplateDTO processTemplateDTO = JSON.parseObject(json, ProcessTemplateDTO.class);
List<NodeJson> nodeList = processTemplateDTO.getNodes();
List<LineJson> lineList = processTemplateDTO.getLines();
// 初始化node. 部分属性后续再注入,因为需要先初始化line对象,类似于spring中的循环依赖,部分属性延迟加载
List<NodeModel> nodeModelList = nodeList.stream().map(ModelParser::nodeBuilder).collect(Collectors.toList());
// 以节点code为key,节点对象为值构建节点对象map,用于line对象的初始化
Map<String, NodeModel> flowNodeMap = nodeModelList.stream().collect(Collectors.toMap(NodeModel::getId, value -> value));
List<LineModel> flowLineList = lineList.stream().map(lineJson -> lineBuilder(lineJson, flowNodeMap)).collect(Collectors.toList());
// 以fromNode和toNode为key分组归类,后续注入到node属性中
Map<String, List<LineModel>> fromLineMap = flowLineList.stream().collect(Collectors.groupingBy(followLine -> followLine.getSource().getId()));
Map<String, List<LineModel>> toLineMap = flowLineList.stream().collect(Collectors.groupingBy(followLine -> followLine.getTarget().getId()));
// 注入到node属性中
nodeModelList.forEach(flowNodeResult -> {
flowNodeResult.setOutputs(fromLineMap.get(flowNodeResult.getId()));
flowNodeResult.setInputs(toLineMap.get(flowNodeResult.getId()));
});
Map<String, NodeModel> nodeModelMap = nodeModelList.stream().collect(Collectors.toMap(NodeModel::getId, value -> value));
// 后续需要在root属性中添加可自行添加
ProcessModel processModel = new ProcessModel();
processModel.setNodes(nodeModelList);
processModel.setNodeModelMap(nodeModelMap);
processModel.setStartModel(buildStartNode(nodeModelList));
return processModel;
}
写在最后
至此解析工作已经做完,到了这一步我们已经将流程定义解析完,得到我们要的对象。下一步我们就要将流程流转起来,也是工作流引擎的最重要一步,下篇文章将会进行介绍。