过程模型
9.1. Overview
流程定义主要基于定向图表示了一个商业流程的规范。定向图包括了节点node和变迁transition。有很多类型的节点,节点的类型定义了运行时的行为,流程定义有且只有一个开始状态。
令牌token是一个执行的路线,token是一个运行时的概念,用来维护图中指想节点的指针。
过程实例process instance是一个过程定义的实现,process instance建立后,一个令牌也为主要执行路线建立了. 这个令牌称为这个流程实例的根令牌,它的位置处于流程定义的开始状态。
信号signal通知token继续graph的执行。当接收到一个匿名的signal,token将通过默认的transition离开当前node;当接收到一个指定的信号,token将通过指定的transition离开node。信号发给一个被委托给根令牌的流程实例。
当一个token到达node,node将执行,节点自己有责任延续图执行。图执行的连续是通过让令牌离开节点来完成的。不同类型的node有不同的执行方式,不能传递执行的node叫做状态state节点。
活动action是过程运行期间由事件event触发的执行(一段java代码),图是软件需求沟通的一种工具,图仅仅是软件的一个视图,他隐藏了很多细节。action则在图外部增加了很多细节,一旦图放在某个地方,它可有动作来修饰,同时加入action不会影响图本身的结构。主要的event包括进入节点、离开节点、执行事务 。
9.2. Process graph
最基本的过程定义是包含了节点和变迁的图,可以通过processdefinition.xml描绘。节点有很多类型如:state, decision, fork, join,... 每个节点又对应一组变迁,变迁通过命令来区分,如下是一个拍卖的过程图和对应的xml:
9.3. Nodes
jbpm提供了一组现成的节点类型给我们使用,同时,我们也可以自己实现自定义的节点类型。
节点的职责,每个节点有2个职责,首先,节点可执行,可执行一定的功能如建立任务实例、发送信息、更新数据库;其次,可以将过程执行进行传播,传播的过程分为几种情况:
- 执行没有传播,这是一个等待状态wait state。
- 通过执行节点的一个离开转换来传播执行. 令牌到达节点是通过一个离开转换API executionContext.leaveNode(String). 现在节点作为可执行一些定制程序逻辑及不用等待连续流程执行的自动节点。
- 建立一个新的执行路线,即节点建立了一个新的令牌,每个新令牌代表了一种新的执行路线,这个新建的令牌将自动跨过变迁。比如一个分叉fork节点。
- 执行的结束,这个节点将结束整个执行路径,同时令牌也将结束。
- 更多的情况是,节点可以更改过程实例的整个运行结构,过程实例的运行结构包括了一棵令牌树,每一个令牌表示一个执行路线,节点则可以建立和结束令牌,将令牌传递到节点,将令牌通过变迁传递等。
JBPM是开放的,可以允许用户定义自己的节点类型。接下来我们看看常用的节点类型:
- 工作节点task-node——代表了人员执行的工作,当执行到达了工作节点,相应的操作者工作列表将增加该项工作实例,之后该节点变为等待状态,这样当人员执行了工作,工作的完成将会将执行传播下去,也就是对令牌发了一个新信号。
- 状态节点state——是一个单纯的等待节点。不同于工作节点的是,没有工作实例生成,可以用在等待外系统。比如当触发了一个节点进入事件,action向外系统发一条信息,接着流程将进入等待状态,当外系统返回响应信息,可以通过token.signal(),触发将会将执行传播下去。
- 判断节点decision——判断根据判断人的不同有2种,一种是内部过程发起判断、一种是外部结果决定判断。
当判断是通过内部过程发起的,将是decision节点。有2种方法指定判断标准,最简单的一种是在变迁中添加条件元素,条件元素是beanshell脚本表达式,返回布尔型。 通过判断条件元素得到返回值然后选择相应的变迁,最先判断为true的条件元素所在的变迁会被选中。另一种方式是通过实现DecisionHandler接口,在java类中判断并选择变迁(DecisionHandler中的判断方法)。
当判断是通过外部(不再过程定义中),你可以用各种变迁离开一个状态或者等待节点,外部触发、等待状态结束、执行传播下去。使用Token.signal(String transitionName) 和 TaskInstance.end(String transitionName)。 - 分叉节点fork——将一条处理路线分成多条并行处理,每一个分支变迁上建立一个子令牌,并生成令牌的父子关系。
- 连接节点join——默认的到达连接节点的子令牌都有共同的父令牌,连接节点将这些子令牌全部结束,然后父令牌将通过唯一的变迁传播,只要有一个子令牌没有结束,则连接节点就处于等待状态。
- 节点节点node——提供给我们自定义节点的功能,可以看作是一个subelement action。当执行到达节点,action会执行;当我们需要自己设定节点的执行,执行我们自己定义的actionhandler,运行完则向下传播执行。不同于action的是节点node执行的是重要商业逻辑,在过程图中是可见的,而action则不可见。
9.4. Transitions
变迁有一个源节点(from)和一个目标节点(to),默认的变迁是变迁列表中的第一个。一般情况下,一个变迁一个名字,名字不重复,如果同一个节点有相同的名字的变迁,排在前面的变迁会先得到,注意当一个节点有相同名字的变迁时Map getLeavingTransitionsMap()得到的数量会少于List getLeavingTransitions()。
默认的变迁是排在第一个的变迁。
9.5. Actions
action是在流程执行中,由事件触发的一段java代码。action和流程图松耦合。action加于event之上和加于node之上是不同的,加于event之上,Action会在事件触发的时候执行,并且不会影响执行的路线,这一点有点像observer观察者模式;action加于node之上,则会影响执行的传播。
这里我们看一个action在event上的例子,假定我们在一个变迁上想更新数据库,数据库更新对于技术实现很重要但对于商业逻辑是不重要的。
public class RemoveEmployeeUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // get the fired employee from the process variables. String firedEmployee = (String) ctx.getContextInstance().getVariable("fired employee"); // by taking the same database connection as used for the jbpm updates, we // reuse the jbpm transaction for our database update. Connection connection = ctx.getProcessInstance().getJbpmSession().getSession().getConnection(); Statement statement = connection.createStatement(); statement.execute("DELETE FROM EMPLOYEE WHERE ..."); statement.execute(); statement.close(); } } |
... ... |
9.5.1. Action configuration
如何配置processdefinition.xml加入action 参见后面的Section 16.2.3, “Configuration of delegations”
9.5.2. Action references
action可以有名字,通过名字可以在不同位置引用相同的action。有名字的action可以被过程定义为子元素而被引用。
9.5.3. Events
事件指定了过程执行的一个片断,在图执行的过程中jBPM会触发事件,确切的时间是jbpm计算下一个状态的时候。事件总是关联过程定义中的一个元素比如节点、变迁。不同的元素触发不同的事件,节点可以触发node-enter event和node-leave event。事件是action的钩子,每一个事件有一组action,当jbpm触发事件的时候,这些action顺序执行。
9.5.4. Event propagation(事件传播)
Superstates建立了在过程定义中元素的父子关系,包含在superstate中的节点和变迁以superstate为父,顶层元素以过程定义为父,过程定义就没有父亲了。如果事件触发了,事件将向上(父亲)传播。这允许在一个流程中一个中心可以捕捉所有的转换事件和同这些事件关联的动作。
9.5.5. Script
脚本是一个action执行的beanshell script,通常过程变量都可以是脚本变量,但是脚本变量不能是过程变量,脚本变量可以如下:
... |
下面采用了variable子元素来定义脚本变量,同时使用了expression子元素定义表达式。
... |
在脚本开始时,过程变量YYY和ZZZ将被分别变成脚本变量b和c,脚本结束时,脚本变量a将保存成过程变量XXX。当access为read,则在脚本开始前会将过程变量赋值给脚本变量,当为write,在脚本结束后将脚本变量赋给过程变量。mapped-name将过程变量映射为另一个名字,可以用在过程变量包含空格等非脚本变量合法字符的转换上。
9.5.6. Custom events
可以自定义事件,事件由图元素(nodes, transitions, process definitions and superstates are graph elements)和事件类型(String)组合定义。虽然jBPM定了事件,但在action中,在自定义node中,甚至在过程实例执行之外,你都可以自定义事件,你通过GraphElement.fireEvent(String eventType, ExecutionContext executionContext)调用,event类型也可以自己指定。
9.6. Superstates(超态)
Superstates是一组节点,可以被递归的嵌套,超态被用来在过程处理中引入层次。 比如,一个应用程序在流程中同步一致聚合所有节点.动作可同超状态事件关联. 一个结果就是令牌可以在给在给定时间的多个嵌套的节点里.这便于检查是否流程执行,比如说在start-up阶段。在jBPM中,你可以随意的在Superstate中组合任意节点。
9.6.1Superstate变迁
所有转换 leaving a superstate 可以由包含在super state 的节点的令牌产生. 转换能到达超状态. 在这种情况, 令牌将重定向到在超状态的第一个节点. 从超状态外面的节点直接转换到超状态内的节点. 当然, 另一个方向循环,从超状态内的节点可以转换到超状态外的节点或者到超状态自身.超状态可以有自引用。
9.6.2.Superstate事件
Superstate有2个事件superstate-enter和superstate-leave。这些事件产生在进入转换节点或离开的时候 当令牌在超状态内开始转换的时候,这些事件不会产生。
注意我们为状态和超状态生成了独立的事件类型. 这让区分的超状态事件和在超状态内传送的节点事件非常容易。
9.6.3.名字分级
节点名字应该一定范围内应该是唯一的,过程定义和Superstate都是一个范围,要在Superstate指定节点,你需要用到“/”,而“..”则指向上一层,看如下的例子
...
...
... ...
9.7. Exception handling
jbpm的异常处理机制是依赖于java异常的,图的处理本身是不会产生错误的,只是执行的代理类产生异常。
过程定义、节点、变迁都可以指定一串exception-handlers,每个exception-handlers又可以指定一串action,
当代理类有异常发s的时候,就会查找相对应的exception-handlers,找到了则执行对应的action。
注意,jbpm的异常处理机制又不同于java的异常处理,在java中,异常捕获会影响控制流,而对于jbpm,控制流则不受影响。异常可以捕捉也可以不捕捉,不捕捉的异常被抛到客户端或者被exception-handlers捉到;捕捉的异常,对于图的执行来看好像没有异常发生。注意:在动作action里处理异常, 通过调用Token.setNode(Node node)把令牌放入图中任意节点是可能的.
9.8. Process composition
jbpm中的Process compositiond即process-state,process-state关联了另一个过程定义,当图的执行遇到了一个process-state,则一个新的子过程实例将创建,父过程将挂起处于process-state处等待子过程的完成。
...
这个hire过程包含了一个process-state,process-state又包含过程interview,当执行运行到process-state,
一个新的过程实例interview建立,hire过程的变量a拷贝到interview过程的变量aa,同样hire过程的变量b拷贝到interview过程的变量bb。当interview结束,interview过程的变量aa拷贝回hire过程的变量a。
9.9. Custom node behaviour
在jbpm,编写自己的节点非常容易,要创建自己的节点,必须实现ActionHandler,可以包含商业逻辑还有就是传播图的执行。下面看一个例子,
public class AmountUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // business logic Float erpAmount = ...get amount from erp-system...; Float processAmount = (Float) ctx.getContextInstance().getVariable("amount"); float result = erpAmount.floatValue() + processAmount.floatValue(); ...update erp-system with the result...; // graph execution propagation if (result > 5000) { ctx.leaveNode(ctx, "big amounts"); } else { ctx.leaveNode(ctx, "small amounts"); } }} |
这个例子首先从ERP系统中读取数量,然后和过程变量中的数量求和,再将结果更新回ERP系统,基于此结果,我们有2个变迁small amounts和large amounts。同样也可以在节点中实现建立和组合令牌。
9.10.图的执行
jBPM中图的执行基于过程定义的解释和命令链模式。
过程定义的解释意味着过程定义的数据要保存到数据库中,在运行时间流程定义信息在流程执行期间被使用 所关注的是:我们用 hibernate's 二级缓存避免在运行时见才载入有关定义信息 由于流程定义不会改变 (参看流程版本) hibernate 缓存流程定义在内存中。
命令链模式表示图中的每一个节点都对应了一个过程执行的传播,如果一个节点不传播执行,这就是一个等待状态。
过程执行从开始一直到它进入了一个等待状态。
令牌代表了一个执行路径,一个令牌就是过程图中一个节点的指针。在等待状态,令牌可以保存到数据库中。这里我们给出一个令牌执行的图,执行起始于一个信号发给了令牌,接着这个执行以命令链模式通过变迁和节点:
当令牌在节点时,信号可以发给令牌,发信号就是通知开始执行,信号还要标明离开当前节点的变迁。第一个变迁是默认的。
当令牌收到信号,
令牌得到当前的节点后调用Node.leave(ExecutionContext,Transition)方法, ExecutionContext可以认为就是令牌,因为它的主要对象是Token。这个方法会触发node-leave事件,
然后调用Transition.take(ExecutionContext)方法,这个方法触发transition事件,
接着再调用变迁目的节点的Node.enter(ExecutionContext)方法,这个方法触发node-enter事件,
接着再调用Node.execute(ExecutionContext)方法,不同的节点类型有不同的执行方法(行为实现)。
总结如下:
Token.signal(Transition)
Node.leave(ExecutionContext,Transition)
Transition.take(ExecutionContext)
Node.enter(ExecutionContext)
Node.execute(ExecutionContext)
注意完成下一个状态的计算,包括调用动作(action)是在在client线程完成的.一个常见的误解是所有的计算 *必须*在client线程里完成.作为任何异步调用 你可以用异步消息(JMS)来完成这个.当消息在同一个事务里被送出的时候同时流程实例更新,必须小心所有的同步问题.一些工作流系统在图中的所有节点之间用异步消息 .但是在高流量环境里,这个算法给业务流程性能调整更多控制和灵活性。
9.11.事物界定
jBPM是在客户端的一个线程以同步方式运行process的。Meaning that the token.signal() or taskInstance.end() will only return when the process has entered a new wait state.
大多数情况下,过程的执行是绑定到server端的事物的,在一个事物中过程从一个状态移动到另一个状态。
jBPM包含一个异步消息系统,允许执行以异步方式执行一个过程。jBPM支持JMS。
当属性async="true",异步节点将不在客户端的线程中执行,而是将消息传给异步消息系统,然后线程返回给客户端(token.signal() or taskInstance.end() will return)