工作流系统之掰洋葱
工作流系统之掰洋葱
作者:荣浩 原文发表于《程序员》0904期
当面对一个完整的工作流系统时,你可能会被它众多的功能所困惑:流程流转模式、时间服务、组织适配、表单权限等等。但是我们可以转换一种思路:从用户使用的角度来进行分析,此时工作流系统的组成就会变得异常清晰。实际在现实开发中,整个系统也是由用户的业务需求一步步迭代而来。
一、 从用户的角度分析工作流系统的组成
1、 面向开发人员的流程设计器
最终用户通过流程设计器对业务流程进行描述,这实际是一个流程建模的过程。理论上,是由业务分析师完成这个业务流程建模的过程,并且业务分析师往往被假定为非技术人员。对于业务分析而言,流程建模通常是抽象的,一定程度上是模糊的,建模的目的在于通过图形的形式向其他人解释一个业务的过程,图形只是一种方式,采用它只是它更易于理解和沟通,实际类似于DSL。实际上企业的规章制度、文字描述的操作规范都是对业务流程不同的描述方式。
对于工作流而言,这个建模所产生的流程定义是需要被引擎执行的。这要求流程中每一个节点的定义都是要有明确含义的,它需要被计算机明确而准确的解释。同时,出于集成业务系统的需要,流程模型定义往往带有很多额外的属性。
2、 工作项列表
即任务列表。工作流系统通过工作项列表进行人工任务的分配。最终用户通过该列表签收、处理每天的工作,工作以工作项的形式展现及导航。对于工作项,用户存在多种业务操作:签收、完成提交、收回、回退。对于分配给他人的工作项,也存在着多种业务操作:催办、提醒、代办、时间限定。
3、 流程追踪
用户在处理工作项时,对该工作在流程中所处的位置进行查看,了解当前流程的状态和执行情况。一般情况下,流程追踪以图形化的方式展现。用户通过不同的图标和标示来区分流程中各个节点的状态和执行信息。
4、 流程实例管理
包括对流程实例、节点实例和工作项实例的管理。改变状态,包括了挂起、重新启动、终止、跳转等等。主要目的在于对流程人为执行错误进行人工干预以及对流程信息的监控。
二、 系统架构
从用户的角度分析完工作流系统的组成,这里从开发人员的角度分析工作流系统的架构。系统架构里的每一部分是如何与用户使用的部分进行对应,以及每一部分在实现时需要注意的事项。
1、 整体构成
如图,各模块分层组织,位于上层的模块依赖于底层的模块。正如你所看到的,流程定义模型位于整个工作流系统的最低层,因为它是整个工作流系统的基础。

流程定义模型:定义对流程进行描述的所有对象。因为对流程进行描述的本质就是利用这些模型进行建模,所以这些模型对象的实现直接决定着工作流系统对流程的描述能力。
组织结构适配器:工作流系统在与业务系统进行集成时,需要进行组织适配,通过这一过程将业务系统里的组织机构导入到工作流系统里。具体实现时,工作流系统需要建立起自己的组织机构模型(包含在流程定义模型里),要适应多种业务系统,往往需要建立多套模型,根据具体情况进行切换。有多种方式完成这个适配,最简单的方式是利用SQL配置读取数据进行语义转换。
流程设计器:供用户使用的可视化图形工具。每种图形都对应着一种流程定义模型。具体的实现有Swing、SWT和Flex,但是基于AJAX或Flex的WEB设计器无疑会给用户提供更好的可用性。
流程执行引擎:将流程定义模型解释为流程实例模型。利用这些流程实例模型完成流程的调度和执行。在工作流系统里,执行引擎是整个系统的核心。实现时不仅需要考虑各种流程调度的实现,还要考虑执行的效率、缓存、日志等等。
工作项引擎:解析参与者定义模型和工作项定义模型,生成相应的工作项。对用户对工作项的操作作出响应。
WEB应用:工作流系统的WEB展现。包括了工作项列表、流程追踪以及流程实例管理的操作和显示。
流程仿真:对建立好的流程模型进行运行仿真,模拟流程模型的执行过程。目的在于发现流程建模过程中的疏漏,发现由此导致的流程不能运行。
时间服务:提供对整个流程实例执行时间和任务执行时间的控制,根据规则触发相应的时间事件,例如任务超时、任务预警等等。根据规则自动触发启动新的流程实例。
业务集成:提供工作流系统与业务系统的契合方式。典型的实现包括通过注册事件监听器和提供接口抽象类调用业务系统代码、提供API给业务系统调用、工作项驱动业务表单和脚本引擎执行业务逻辑脚本等等。特定于工作项驱动业务表单,为方便开发,绝大多数的工作流厂商都提供了电子表单的实现。
2、 基于事件的流程执行引擎
流程执行引擎的主要职责就是负责流程的调度和执行。
首先需要将流程定义模型解释为流程实例模型,在定义模型和实例模型之间建立起对应关系。一个简单的对应关系如下图所示:

执行引擎将流程定义模型的属性读取到相应的实例模型里,由实例模型完成流程的调度和执行。当然,上图只是一个简单的描述,实际情况要复杂的多,特别是节点定义(ActivityDefinition),根据实际应用,往往存在着多种类型,典型的有开始节点(StartDefinition)、任务节点(TaskDefinition)、自动节点(AutoDefinition)、分裂节点(SplitDefinition)、汇聚节点(JoinDefinition)、结束节点(EndDefinition)等等。这些节点的实例根据类型的不同执行不同的逻辑。其中,分裂节点实例和汇聚节点实例负责流程的调度,它们决定流程的流向,通常情况下,它们会调用一个脚本引擎执行一段脚本来决定流程的流向,同时,也会提供对外暴露的接口,由业务系统实现,接口返回的结果决定流程的流向。任务节点实例和自动节点实例则负责流程的执行,为保证流程执行引擎职责的清晰以及对外围设施的松耦合,它们只是发布相关的事件,通过事件发布/订阅机制来触发具体的逻辑执行。

典型的事件有流程启动事件、流程结束事件、进入节点事件、离开节点事件、时间事件等。例如,任务节点实例的进入节点事件将会触发工作项引擎生成工作项(Workitem),并触发时间服务器开始计时。
EventInfo定义事件。EventHandle发布事件,ActivityInstance和ProcessInstance继承EventHandle,在相应位置发布事件。
EventInfo.java EventHandle.java log.debug("event '" + eventType + "' on '" + graphInfo + "' for '" + executionContext + "'"); try { executionContext.setEventSource(graphInfo); fireEvent(eventType, executionContext); } finally { executionContext.setEventSource(null); } }
TaskInstance.java
/** * 节点初始化 */ public void initial (ExecutionContext executionContext){ //触发活动节点初始化事件 fireEvent(EventInfo.EVENTTYPE_ACTIVITY_INIT, getActivityDefinition(), executionContext); }
事件的发布和订阅机制利用Spring的事件机制实现。
3、 基于充血模型的工作项引擎
对最终用户而言,大部分的业务操作都集中在对工作项的操作上。常见的包括工作项的提交、收回、委派、追加和退回。这些操作从系统设计的角度不仅涉及到工作项(Workitem)对象内部状态的变化,而且影响到流程执行引擎的调度以及相关的其他工作项对象状态。
工作项引擎的职责包括两部分。第一,监听任务节点实例的进入事件,生成工作项实例。第二,处理上面提到的各种工作项操作。

实现时,工作项生成器根据任务参与者的执行模式典型的分为四种情况:
竞争参与,当有多个参与者参与该任务时,产生竞争,谁先开始这项工作,就由谁负责完成该工作。此时,工作项生成器生成多个工作项实例,在某个工作项完成时会终止其余工作项。
顺序参与,多个参与者按照指定的顺序完成该工作。A完成之后由B完成,B完成之后再交给C完成。此时,工作项生成器生成多个工作项实例,根据顺序依次激活各个工作项。
共同参与,多个参与者同时对工作进行处理。此时,工作项生成器生成多个工作项实例并全部激活。
智能决策,存在多个参与者的情况下,工作项生成器能够根据一定的指标(由数据分析,例如人员的处理效率,工作负载等等)和规则来决定该节点的参与者并为其生成相应工作项。这里涉及到算法。
对于工作流系统而言,各种流程实例对象都是充血模型。特定于各种工作项操作的处理,此时的工作项对象亦设计为充血模型,将业务逻辑封装到领域模型里,简化领域模型之间的交互,省去频繁的get/set。由领域模型再委派到具体的处理类里。
Client->(Business Facade)->Domain Model->service->Data Access(DAO)
Workitem.java
/** * 签收 * * @param userId 签收人 * @param userName 签收人名称 */ /** * 委派工作项 顺序参与同时参与有效 * 角色型以及竞争参与无效 * * @param userId 被委派人ID * @param userName 被委派人NAME */ /** * 取消委派 顺序参与同时参与有效 * 角色型以及竞争参与无效 */ public abstract void cancelDelegate(); /** * 竞争参与情况下,竞争到的人员取消竞争,恢复重新竞争 */ public abstract void cancelCompete(); /** * 提交 */ public abstract void commit(); /** * 挂起 */ public abstract void suspend(); /** * 恢复挂起 */ public abstract void resumeSuspend(); /** * 手工结束 */ public abstract void terminate(); /** * 收回 */ public abstract void callback();
Workitem将逻辑委派给IWorkitemExecutor接口,WorkitemExecutor实现IWorkitemExecutor并由Spring BeanFactory注入到Workitem,WorkitemExecutor根据参与者对任务的执行模式再进行分发处理。
WorkitemExecutor.java
private IWorkitemExecutor synchWorkitemExecutor; private IWorkitemExecutor sequenceWokitemExecutor; private IWorkitemExecutor competeWorkitemExecutor; private IWorkitemExecutor balanceWorkitemExecutor; /** * 提交工作项 * * @param workitem 工作项 */ public void commitWorkitem(Workitem workitem) { getWorkitemExecutor(workitem).commitWorkitem(workitem); } /** * 根据参与模式返回活动对应的工作项执行器 * * @param workitem 执行模式 * @return 工作项执行器 */ protected IWorkitemExecutor getWorkitemExecutor(Workitem workitem) { int executeMode = workitem.getExecuteMode(); IWorkitemExecutor executor = null; switch (executeMode) { case WorkitemInfo.EXECUTEMODE_SYNCH: executor = synchWorkitemExecutor; break; case WorkitemInfo.EXECUTEMODE_SEQUENCE: executor = sequenceWokitemExecutor; break; case WorkitemInfo.EXECUTEMODE_COMPETE: executor = competeWorkitemExecutor; break; case WorkitemInfo.EXECUTEMODE_BALANCE: executor = balanceWorkitemExecutor; break; } return executor; }
4、 工作项驱动业务表单的业务集成方式
最终用户对任务的处理,必然由工作项对应着某一业务表单。用户在工作项列表里选择自己需要办理的工作项,由工作项导航到业务表单。
特定于WEB系统,业务表单的导航由url完成。在流程定义模型设计时,将url设置入节点属性,生成工作项时将此url保存在工作项对象属性里。点击工作项详细信息时即打开该url,完成到业务表单的导航。业务表单页面通常需要引入处理工作项逻辑的父页面或者导入定制的js库,这些父页面或js库由工作流产品提供。这样,对于业务表单编写,工作流逻辑是透明的。

不错
世界上本没有路,走的人多了,有路也没用