微型工作流引擎设计
一、前言
提到工作流很多人就会想到OA,的确OA就是典型的工作流的应用,但是工作流并不仅仅局限于OA,工作流应该算是基础框架软件,主要用于流程的重组和优化,它有广阔的应用领域。在java下有很多优秀的开源工作流可以选择比如activit5、jpbm4等,在.net下却几乎找不到令人满意的工作流引擎可用。当然不是说.net下没有开源的只是有些国产开源的但看了代码后就一点兴趣都没有了,且不说代码质量如何,还引入了一大堆的东西,想在项目中应用也是非常困难。鉴于此我还是决定自己开发一款.NET微型工作流引擎。
二、基本说明
为什么叫微型工作流引擎?就是超轻量级,以方便在项目中轻便的使用,比如只有一个类库dll,大小也就几百k到1M左右,不过我们要先回过头来看看工作流系统,它实在是太大了,它应该包括:
1、工作流引擎
2、工作流设计器
3、工作流管理系统
4、表单设计器
目前来说的我只实现了核心引擎,流程定义也只能先在xml中编辑然后读取到引擎中或者直接定义到数据库中,但整个流程是能够正常流转。至于流程设计器、表单设计器、工作流管理系统这个我有精力了再慢慢开发。这里我完成的只是很小的一块,但是是工作流的核心,可以很方便的嵌入到业务系统中应用。
引擎主要提供了对于工作流定义的解析以及流程流转的支持。工作流定义文件描述了业务的交互逻辑,工作流引擎通过解析工作流定义文件按照业务的交互逻辑进行业务的流转,工作流引擎通常通过参考某种模型来进行设计,通过调度算法来进行流程的流转(流程的启动、终止、挂起、恢复等),通过各种环节调度算法来实现对于环节的流转(环节的合并、分叉、选择、条件性的选择等)。
三、初步印象
1、从概念开始解释估计大家都会看不下去了。我们先拿一个简单实例来看看,新建一个项目,引用我的工作流引擎类库(Chitu.Bpm.dll,取名为赤兔)。
在项目启动时配置流程引擎(Global.asax.cs中),如下:
//初始化流程引擎 BpmConfiguration .Instance() .Config(@"C:\Configration\BpmConfig.xml") .Start();
在项目中使用时,比如新建流程定义:
//取得工作流上下文 var bpm = new BpmContext() .UseTransaction(true) .SetActor("萧秦"); //新增流程定义 bpm.NewProcessDefinition("请假流程") .SetXmlFile(@"C:\Definition\demo1.xml") .SetCategory("分类1") .SetEffectDate(DateTime.Now) .SetExpireDate(DateTime.Now.AddDays(180)) .SetMemo("memo1") .Create() //创建流程定义,只生成bpm_definition_process表 .Parse() //解析xml .Deploy(); //发布流程
启动流程:
//启动流程 var process = bpm.NewProcessIntance("请假流程ID", "萧秦(业务ID)"); //创建流程实例 process.SetVariable("流程变量1", "值1"); //设置流程变量 process.Start();
人工任务节点转交下一步:
//任务完成 var task = bpm.LoadTaskInstance("任务ID"); task.SetVariable("任务变量2", "xx"); task.Signal(); //触发令牌流转
所有的操作都通过Facade模式集中到BpmContext中,操作简单方便。
2、接下来我们先看看流程定义的XML,以下是我捏造的一个流程,以便把各种节点都放进去了。
<?xml version="1.0" encoding="UTF-8"?> <process name="样板房装修流程"> <start name="装修申请"> <transition to="装修方案设计" > <action class="Namespace.MyActionHandler"></action> </transition> </start> <task name="装修方案设计"> <transition to="装修方案审核"> <action script="log.Debug(‘装修方案审核 action test‘);"></action> </transition> </task> <decision name="装修方案审核"> <transitions> <transition to="装修筹备" condition-expression="variable.pass == true"></transition> <transition to="装修方案设计" condition-expression="variable.pass != true"></transition> </transitions> <events> <action event="enter" class="enterHandlerClass"></action> <action event="leave" class="leaveHandlerClass"></action> </events> <assignments> <assignment owner="{process.starter}"></assignment> </assignments> <variables> <variable type="boolean" name="IsPass" access="read,required"></variable> </variables> </decision> <fork name="装修筹备"> <transition to="装修合同签定"></transition> <transition to="等待装修工人到位"></transition> <transition to="装修材料预算"></transition> </fork> <sign name="装修合同签定" necessary="false" async="true"> <transition to="装修施工"></transition> </sign> <wait name="等待装修工人到位"> <transition to="装修施工"></transition> </wait> <task name="装修材料预算"> <transition to="材料采购子流程"></transition> </task> <subflow name="材料采购子流程"> <transition to="装修施工"></transition> </subflow> <join name="装修施工"> <transition to="施工验收/付款"></transition> </join> <auto name="施工验收/付款"> <transition to="装修完成"></transition> </auto> <end name="装修完成"> </end> <events> <action event="process-start" class="TestStartHandler"></action> <action event="process-end" class="TestEndHandler"></action> </events> <variables> <variable type="string" name="start_id" access="readonly,required"></variable> <variable type="string" name="start_person"></variable> </variables> </process>
定义的根节点为流程(process),流程下为各个任务节点(node),任务节点分为:
start 开始节点
auto 自动节点
task 人工节点
decisioin 决策节点
fork 发散节点
join 聚合节点
sublfow 子流程节点
sign 会签节点
wait 等待节点
end 结束节点
任务节点下可以包括路由(transition)动作(action)及人员分配(assignment)变量定义(variable)
其中action包括几种类型:1、class 2、script 3、sql 4、webservice 5、expression
在script或expression中可以直接访问process.xxx属性或task.xxx属性或variable.xxx简化了动态c#语句的使用。
当然XML定义中还有很多其它的属性定义我这里也没有都列出来,以后用到了再仔细说。
3、关于数据库设计,这里仅仅是流程流转核心所需要的表,表之间都没有拉关系
四、部分功能剖析
a、我把它划分为主要的几大模块: 引擎配置、流程定义、实例流转、日志处理、计划任务
引擎配置:配置引擎实例的数据库连接、日志配置、参数设定等。
流程定义:利用xml来描述流程,主要定义任务节点,路由、动作事件、变量、人员分配等
实例流转:根据定义运行流程实例
日志处理:输出日志
任务计划:会启动一个服务,用于处理比如延时启动,任务过期等
b、流转中的关键性类设计包括:
1、流程对象(Process)
2、工作任务(Task)
3、路由(Trasition)
4、令牌(Token)
5、事件总线与动作处理 (EventBus、ActionHandler)
6、人员分配及委托机制(Assignment、Depute)
7、流程回退处理(RollbackService)
8、消息服务(NotifyService)
c、通常引擎控制流程调度流转核心的调度算法主要有FSM以及PetriNet两种,基于调度算法来完成流程的流转:
1、FSM(有限状态机)
FSM 的定义为包含一组状态集(states)、一个起始状态(start state)、一组输入符号集(alphabet)、一个映射输入符号和当前状态到下一状态的转换函数(transition function)的计算模型。当输入符号串,模型随即进入起始状态。它要改变到新的状态,依赖于转换函数。在有限状态机中,会有有许多变量,例如,状态机有很多与动作(actions)转换(Mealy机)或状态(摩尔机)关联的动作,多重起始状态,基于没有输入符号的转换,或者指定符号和状态(非定有限状态机)的多个转换,指派给接收状态(识别者)的一个或多个状态,等等。遵循FSM流程引擎通过状态的切换来完成流程的流转。
2、PetriNet
信息流的一个抽象的、形式的模型。指出一系统的静态和动态性质。PetriNet通常表示成图。遵循PetriNet流程引擎通过令牌来决定流程的流转。
我采用的是第二种PetriNet算法。用Token来表示当前实例运行的位置,也利用token在流程各个点之间的转移来表示流程的推进,如下图所示:
令牌流转逻辑,我把以下类方法都做一个简化省略了路由选择及节点处理细节,好让大家明白令牌的流转:
//令牌Token类中Signal public void Signal() { fromTask.Leave(executeContext); } //任务Task类中的Leave public void Leave(ExecutionContext executionContext) { transition.Take(executionContext); } //路由Transition类中的Take public void Take(ExecutionContext executionContext) { toTask.Enter(executionContext); } //任务Task类中的Enter public void Enter(ExecutionContext executionContext) { Run(executionContext); }
至此令牌成功的从一个节点转移到下一个节点了,令牌的流转是工作流的关键,当然不同的节点处理是有所不同的,其中最复杂的当数发散节点及聚合节点了。这里就介绍到这里,不再给大家详细介绍了。
d、目前我引擎中实现的主要包括以下功能:
1、解释过程定义
2、控制过程实例—创建、激活、挂起、终止等
3、控制流程调度流转
4、自定义动作及事件发布
5、流程变量及工作变量处理
6、任务计划,比如延时启动,任务过期等
7、委托服务,委托代办
8、回退服务,回退到任意节点或召回
9、消息服务,比如认领通知、待办提醒、催办消息…
10、流程任务监控服务
11、日志处理及历史记录
12、任务分配与认领
13、参与者组织模型接口
五、总结
目前我的这款工作流引擎还在继续完善当中,我总结下它的优缺点:
优点:
1、它是一款超轻量极或者说是微型的工作流引擎,而且绿色无污染,它只有一个dll,大小仅1M左右。
2、它目前支持SQL Server、MySql、Oracle、SQLite、PostgreSql等多种数据库
3、体型上来说它虽然是微型,但功能上并不算微型,它的设计结合了现代的OA及传统工作流、基本上可以实现我们大多数的功能需要。
4、它其实是面向开发者设计的,从上面初始印象中的实例代码中大家可以看到,它的接口是很集中、精简、友好的,让开发者容易理解而且使用起来更方便简单。所以它更适合嵌入到项目中开发。
缺点:
1、它现在没有流程设计器、管理系统、表单设计器等,充其量只能算是一个类库,并不是直接拿来就可以使用。
2、目前刚刚完成第一个内部版本,而且目前只在我们内部项目中使用,所以它不够成熟,虽然我们会持续的改进和完善。
3、缺乏成功应用的案例。
对于我们自己来说,这些缺点都是我们需要继续努力的地方,可能还需要大量的时间来完成。目前来说我们还不打算开源,等它慢慢稳定成熟后我们会考虑是不是开源出来。如果大家有好的建议或有哪方面的疑惑我很乐意给大家解答,或者你也在设计开发自己的工作流,我们可以相互交流下。