原文地址:http://www.blogjava.net/RongHao/archive/2007/10/30/157009.html
对于当前的工作流应用来说,人工节点无疑扮演着一个非常重要的角色。因为无论是传统的协同办公系统还是越来越多的企业应用,都需要有人参与到具体的流程中来。这篇文章着重分析工作流应用中人工节点所涉及到的参与者模式以及与此关联的工作项模式,最后会提供部分的解决方案作为参考。
先简单说说什么是人工节点。
人工节点的概念是与自动节点相对的。顾名思义,人工节点就是需要有人参与的节点,在实际流程中,它体现在产生由人完成的工作项以及由人决定一些决策变量,这些决策变量会对流程的运行产生影响(例如分支的选择等等)。自动节点则是由工作流引擎自己调用完成,不需要人的参与,通常是执行定制的业务操作。相比较而言,人工节点更多的应用在管理流程里,而自动节点更多的则是应用在企业业务流程里。
人工节点的职责有三个:第一是决定该节点的参与者;第二是根据参与者生成需要人来处理的工作项;最后是当工作项被参与者处理完毕时,它将继续触发流程的流转。参与者处理工作项时可以处理相应的业务和设置流程决策变量。
下面我们就按照这三个职责分别对人工节点所涉及到的参与者模式和工作项模式进行分析。
1、 决定参与者模式
换句话说就是决定该节点的参与者,这里有两种模式:引擎自动获取和最终用户指定。
1.1引擎自动获取
所谓引擎自动获取就是由引擎在运行期计算实际的节点参与者,不需要最终用户的参与。这个计算基于流程定义时对该节点参与者的定义。
(1)
直接指定人员、部门或角色
这种情况最简单,也最直接,用户定义节点时直接在组织用户树里选定人员、部门或角色,然后在运行期根据定义执行与或者是或的运算。大多数的工作流引擎都支持这种模式。但很明显它也存在着很大的局限性,它是静态的,一旦流程定义完毕参与者也就跟着固定下来,运行期的任何变化都不会对参与者造成影响,一个很简单的需求,请假流程,节点的参与者需要是当前申请者的部门领导,因为申请者在定义期是不确定的,所以根本无法指定节点的参与者,所以这种模式远远满足不了用户稍微复杂一点的需求。
(2)
调用用户定制的计算参与者代码
这种情况通常是由引擎提供一个接口或是父类,用户需要实现或是继承这个接口或父类,然后实现相应的方法。这个方法通常会传递进一个执行上下文的参数,用户代码通过这个上下文可以访问到当前流程实例的信息,例如当前节点状态,工作流变量等等,然后用户可以根据实际业务和当前流程实例信息进行逻辑计算,最后返回一个参与者的ID集合。对于上一个模式里提到的计算当前申请者部门领导的例子,这个模式实现起来非常简单,首先获得当前申请者ID,然后在根据这个ID找出该申请者部门再找出该部门领导即可。
实际流程运行到该节点就会调用用户自己定制的计算参与者的代码,方法返回的参与者ID即作为该节点的实际参与者。
这种模式对于工作流引擎的实现而言最为简单,因为它把最大的复杂性都抛给了用户,由用户代码来计算实际的参与者。实际上很多开源的工作流引擎采用的都是这种方式,例如JBPM。但是这种方式给用户带来最大灵活性的同时也带来了复杂和烦琐。特别是当面对一个数量巨大的流程需求时,为每一个流程的每一个人工节点都定义一个参与者计算类是让人头疼的。再加上现在强调业务的敏捷,业务里的改变要迅速反馈到流程的定义里,让最终用户来编写或修改这个参与者计算类不现实也不可能。补充一下,这也是用户在考虑采用开源的工作流引擎还是商业工作流引擎时需要着重考虑的一个方面。
(3)
指定前续节点的参与者
实际上是用户在节点定义时指定参与者为前续某个节点的参与者,当流程运行到该节点,引擎会自动获取所指定的前续节点的参与者作为该节点的实际参与者。
这个模式实现起来并不困难,大多数商业工作流引擎都对该模式进行了支持。它能够满足用户的部分特定需求。
(4)
更为复杂的情况
用户的需求永远是复杂的,引擎所要做得就是尽量降低这种复杂性,流程的变化要能够迅速跟上业务的变化。考虑下面两种稍微复杂一点但是又很常见的需求。需求一:参与者为当前申请者的部门领导且职位为副总;需求二:参与者需要是测试部的所有女同事。这两种需求模式1、3都不能满足,2可以,但是正如提到的那样,模式2可能会非常的烦琐,不能适应业务的敏捷。其实这里的复杂性主要体现在:1、这里的参与者可能是运行期决定的;2、参与者的限制条件可能非常多,而这些条件不是简单的部门、角色或职位所能描述的。
对于一般的工作流引擎而言,它们都会选择模式2的实现,让用户自己实现逻辑。实际在后面的部分解决方案里,我们会看到更为好一点的实现方式。
1.2最终用户指定
运行期由最终用户来决定节点的参与者。这也是中国国情所独有的特色。这种模式最为常见的就是用户提交工作项时的提交页面,用户在该页面上选定下一节点(多数分支用户选定时)和下一节点的参与者。这种模式本身并不困难,问题在于在提交页面需要给用户提供一个参与者的选择范围,让用户进行选择。而关于这个选择范围,则又回到前面所提到的引擎自动获取的模式,这个范围同样是需要引擎计算的。于是又回到了刚刚讨论过的四种模式。
2、参与者执行模式
现在,已经获得了节点的参与者。引擎下一步将会根据这个参与者生成工作项,注意,这里的参与者可能是一个人,也可能会是一个人员范围(即多个人)。于是就产生了参与者的执行模式,也可以理解为工作项的生成模式。
2.1竞争参与
当有多个参与者参与这个节点时就会产生竞争参与这个模式。同样一个工作,A可以完成,B也可以完成,于是就产生竞争,谁先开始这项工作,就由谁负责完成该工作。
2.2顺序参与
多个参与者按照指定的顺序完成该工作项。A完成之后由B完成,B完成之后再交给C完成。
2.3同时参与
多个参与者同时对工作进行处理,所有参与者均完成后,流程继续向后流转。这个模式其实比较复杂,因为这里同时涉及到一个完成规则:是所有参与者均完成工作项后流程流转,还是有其他规则?例如完成2个工作项即可流转,完成80%的工作项即可流转。稍候会讨论到。
2.4负载均衡
这也是一个常见的需求。这项工作A和B都可以完成,但是A目前有10个待办工作项,B只有2个待办工作项。于是用户期望该工作交由B来完成。这里需要实现一个简单的负载均衡。其实这种情况只是智能决策的一种最简单的情况,所谓智能决策是指系统能够根据一定的指标(由数据分析,例如人员的处理效率,工作负载等等)和规则来决定该节点的参与者。
3、工作项完成模式
这个模式在参与者执行模式为同时参与时有效。在说到这个模式之前,先简单说说工作项可能存在的几种特殊状态,这些状态包括挂起、人工终止和委派。挂起就是工作项暂时停止执行,挂起会影响到流程的流转,会导致流程的挂起。人工终止则是人工手动改变该工作项的状态,使该工作项终止执行,这个人通常会是管理员。人工终止也会对流程流转产生影响,当除去该工作项之外的所有工作项都完成时,人工终止该工作项会触发流程的流转。委派就是将该工作项委派给他人完成,同时该工作项也就结束了。人工终止和委派是工作项结束的特殊状态。
3.1全部完成
当所有工作项都结束时触发节点的结束和流程的流转。
3.2完成规定的个数
节点定义时指定工作项必须完成的个数,当完成的工作项达到这个指定的个数时触发节点的结束和流程的流转。
3.3完成规定的百分比
节点定义时指定工作项必须完成的百分比,当完成的工作项占所有工作项的比例达到这个指定的百分比时触发节点的结束和流程的流转。
其实这里很明显的可以看出不管是所谓的参与者执行模式还是工作项完成模式不过都是一定的规则,既然是一定的规则那必然就限定了应用的灵活性,用户能否自定义规则?根据业务灵活地修改规则?规则引擎+DSL应该是一个不错的选择。
posted @
2007-10-30 18:06 ronghao 阅读(3654) |
评论 (3) |
编辑 收藏
周末参加了清华大学信息系统与工程研究所组织的一次工作流研讨会。
会上清华大学的研究人员介绍了他们对工作流研究的一些成果。其中主要关注了数据挖掘和分析。
关于数据挖掘,有两个方面。第一个方面是当企业没有应用工作流时,对企业日常数据进行挖掘,分析数据
之间的关联和逻辑,得出一套基本的企业应用业务流程,为将来应用工作流做出准备;第二个方面是当企业
已有工作流应用时,对相关流程数据进行挖掘,根据数据描绘出正在运行的流程,很显然,这个方面与流程
仿真有关。
关于数据分析,则主要是对流程数据进行分析,这个涉及的内容就比较多,包括流程的运行效率,人员效率
分析,统计等等。其中有一个非常有趣的例子:贪官A试图逃跑,逃跑前是有一定前兆的,比如向国外转移
账款,给小蜜频繁打电话,工作效率降低等等。于是数据分析就可以起作用,对A最近的所有数据进行分析,
得出他将要逃跑的结论。而来自有生博大的赵斌,谈论到他们已经实施的项目中,涉及到对一些电子公文
流程中的办文统计,并依据“指标”进行工作量和效率的分析。
这其实也反映出我们工作流软件中缺失的部分:数据分析-数据统计-优化流程。而这也正是BPM的重要功能之一。也是未来发展的趋势。功能缺失最重要的原因在于实际上我们的软件没有研发只有开发。具体他们略微谈到了他们一些实现的方式,非常的理论,涉及到算法,数据结构等等。我们软件开发最主要的是满足用户的各种业务需求,比如说回退、收回、会签等等,这也是现实情况所决定的,开发团队的人员通常都不是很多,并且这种分析挖掘需要的人员无疑需要很高的理论水准,这个本科往往是达不到的(不得不承认研究生和博士就是比本科要优秀),博士硕士的研发团队的开发成本会非常的高,并且实际产生的利润很难评价,对于不大的公司来讲这个是很难接受的。于是就造成了这种状况:软件研发缺失掉了。
依托高校是个不错的想法,但是依托高校不等于说你把办公地点搬到清华大学门口就是依托高校了,也有这种依托,那是买煎饼的。
posted @
2007-10-29 11:57 ronghao 阅读(1396) |
评论 (6) |
编辑 收藏
问题背景:我们是一家工作流公司,客户采购我们的产品后,将其嵌入其项目中。我们的工作流采用的是 spring+hibernate的方式,客户项目则是jdbc直接进行数据库操作。
问题:客户在其数据库操作过程中需要调用我们的工作流接口,这样就需要将我们的工作流操作与他们的业 务操作置于同一个事务中。我们的服务采用的都是spring的声明式事务,而客户采用的是对 connection进行事务处理。如何保证事务的一致性?
想到的解决方案一:使用jta事务,用tomcat+jotm提供事务管理器。为什么一开始就想到要使用jta事务??实际上我们和客户都是使用的同一个数据库,为了方便,各自使用了不同的数据库连接方式,使用jta的话确实有bt的意思在里面。但是事实上是我们的第一反应都是jta。最后没有采用该方法的原因也很简单:我没有将jotm配置成功!汗一个。
想到的解决方案二:将客户的这些特定代码用spring管理起来。因为要修改客户部分代码,这个方案遭到了客户的强烈反对。于是放弃。
想到的解决方案三:客户数据库操作与我们的服务使用同一个数据库连接。然后编程处理事务。存在两种方式:一种是把客户的连接传给我们,另一种则是把我们的连接传给客户。第一种方式对我们的影响太大,所以最后决定采用后一种方式:从hibernate session中获取connection然后传递给客户。接下来查看一下HibernateTemplate的execute()方法,思路就很简单了:获取定义的sessionFactory-->创建一个新的session并打开-->将session与当前线程绑定-->给客户代码返回connection-->打开事务-->客户使用我们传递的connection进行数据库操作-->我们不带声明事务的服务操作-->提交事务-->解除绑定。
实际要注意的地方是:1、将session与当前线程绑定使用的TransactionSynchronizationManager.bindResource()方法,这样在HibernateTemplate里才能找到session;
2、我们的服务一定要把声明式事务彻底干掉,否则会有commit;
3、我们服务调用完毕后一定要flush session,否则客户代码不会感知数据库里的数据变化。
最终解决:使用了spring里常用的模板和回调。代码如下:
public class TransactionTemplate {
protected final Log logger = LogFactory.getLog(TransactionTemplate.class);
private FlushMode flushMode = FlushMode.ALWAYS;
public Object execute(TransactionCallback callback) {
//首先获取sessionFactory
SessionFactory sessionFactory = (SessionFactory) Framework.getEngine()
.getContainer().getComponent("sessionFactory");
//创建一个新的session并打开
logger.debug("Opening single Hibernate Session in TransactionTemplate");
Session session = getSession(sessionFactory);
//将session与当前线程绑定
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
//获取数据库连接
Connection conn = session.connection();
Object result = null;
Transaction transaction = null;
try {
//开始处理事务
transaction = session.beginTransaction();
try {
result = callback.doInTransaction(conn);
}
catch (RuntimeException ex) {
doRollback(session, transaction);
throw ex;
}
catch (Error err) {
doRollback(session, transaction);
throw err;
}
//如果数据库操作过程中没有发生异常则提交事务
transaction.commit();
} catch (WorkflowException e) {
logger.error("数据库操作失败,事务回滚也失败!");
throw e;
} catch (RuntimeException ex) {
logger.error("数据库操作失败,事务被回滚!");
throw ex;
} catch (Error err) {
logger.error("数据库操作失败,事务被回滚!");
throw err;
} finally {
// 将session与当前线程解除绑定
TransactionSynchronizationManager.unbindResource(sessionFactory);
doClose(session);
}
return result;
}
protected Session getSession(SessionFactory sessionFactory) {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
FlushMode flushMode = getFlushMode();
if (flushMode != null) {
session.setFlushMode(flushMode);
}
return session;
}
private void doRollback(Session session, Transaction transaction) {
logger.debug("数据库操作异常,开始回滚事务");
try {
transaction.rollback();
logger.debug("回滚事务成功!");
}
catch (Exception e) {
logger.error("回滚事务失败!");
throw new WorkflowException("回滚事务失败!");
} finally {
session.clear();
}
}
private void doClose(Session session) {
logger.debug("开始关闭连接");
try {
session.close();
}
catch (Exception e) {
logger.error("关闭连接失败!");
throw new WorkflowException("关闭连接失败!");
}
}
public FlushMode getFlushMode() {
return flushMode;
}
public void setFlushMode(FlushMode flushMode) {
this.flushMode = flushMode;
}
}
public interface TransactionCallback {
Object doInTransaction(Connection conn);
}
调用伪代码:
public void methodA(){
TransactionTemplate transactionTemplate=new TransactionTemplate();
transactionTemplate.execute(new TransactionCallback(){
public Object doInTransaction(Connection conn) {
//客户代码
client.method1("1");
//我们代码 直接使用
our.method2();
//客户代码
client.method3("l");
return null;
}
});
}
posted @
2007-10-09 15:11 ronghao 阅读(7736) |
评论 (5) |
编辑 收藏
基于职责设计对象
最关键的软件开发工具是受过良好设计原则训练的思维,而不是UML或其他任何技术。
RDD是思考OO软件设计的一般性隐喻。把软件对象想象成具有某种职责的人,他要与其他人协作以完成工作。RDD使我们把OO设计看作是有职责对象进行协作的共同体。
分配职责的基本模式是GRASP。
创建者模式
问题:谁创建类A的实例?
建议:如果以下条件为真时(越多越好),将创建类A实例的职责分配给类B:
1、B“包含”或组成聚集了A。
2、B记录A。
3、B紧密的使用A。
4、B具有A的初始化数据。
首选聚集或包含A的类B。
注意:A和B指的都是软件对象而不是领域模型对象。
禁忌:对象的创建常常具有相当的复杂性。在一些情况下,更适合使用工厂。
信息专家模式
问题:给对象分配职责的基本原则是什么?
建议:把职责分配给具有完成该职责所需信息的那个类。把职责和数据置于一处,把服务与其属性置于一处。
禁忌:相比较而言系统关注更为重要,设计要分离主要的系统关注。例如:领域对象加入持久化逻辑成为充血模型,这就把应用逻辑与数据库逻辑混合起来了(不良设计)。
低耦合模式
问题:如何减少因变化产生的影响?(高耦合并不是问题所在,问题是与某些方面不稳定的元素之间的高耦合)
建议:分配职责以使(不必要的)耦合保持在较低的水平。(信息专家模式支持低耦合)
控制器模式
问题:在UI层之上首先接受和协调系统操作的对象是什么?
建议:把职责分配给能代表下列选择之一的对象:
1、代表全部“系统”、“根对象”、运行软件的设备或主要的子系统(外观控制器的变体)。
2、代表发送系统操作的用例场景(用例或会话控制器)。
高内聚模式
问题:怎样使对象保持有内聚、可理解和可管理,同时具有支持低耦合的附加作用?
建议:职责分配应保持高内聚。委派职责。内聚性低的类通常表示大粒度的抽象,或承担了本该委托给其他对象的职责。
多态模式
问题:如何处理基于类型的选择?如何创建可插拔的软件构件?
建议:当相关选择或行为随类型(类)有所不同时,使用多态为变化的行为类型分配职责。不要测试对象的类型,也不要使用条件逻辑来执行基于类型的不同选择。
经验:如果有一个具有抽象超类C1的类层次结构,可以考虑对应于C1中的公共方法特征标记来定义接口I1,然后声明C1来实现接口I1。
纯虚构模式
问题:当并不想违背高内聚和低耦合或其他目标,但基于专家模式的方案又不合适时,哪些对象应承担这一职责?
建议:人为制造类,由该类来承担一组高内聚的职责。该类并不代表问题领域的概念-是虚构的事物。例如DAO
纯虚构通常基于相关的功能性进行划分,因此这是一种以功能为中心的对象。
间接性模式
问题:为了避免两个或多个事物之间的直接耦合,应该如何分配职责?
建议:将职责分配给中介对象。例如,Adapter
间接性的动机通常是为了低耦合,并且大多数间接性中介都是纯虚构的。
防止变异模式
问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响?
建议:创建稳定的接口。
不要和陌生人讲话:约束了应该在方法里给哪些对象发送消息。它要求在方法里,只应给以下对象发送消息:
1、this对象(自身)
2、方法的参数
3、this的属性
4、作为this属性的集合中的元素
5、在方法中创建的对象
典型违反该原则的例子: F f=foo.getA().getB().getC().getD().getE().getF();
命令-查询分离原则:
执行动作(更新、调整,等等)的命令方法,这种方法通常具有改变对象状态等副作用,并且是void的(没有返回值)。
向调用者返回数据的查询,这种方法没有副作用,不会永久性地改变任何对象的状态。
关键在于:一个方法不应该同时属于以上两种类型。
在绘制交互图时考虑和决定职责分配。然后在类图的方法分栏里概括职责分配结果,方法是职责的具体实现。
posted @
2007-09-04 17:38 ronghao 阅读(1282) |
评论 (0) |
编辑 收藏
在迭代开发中,我们并非一次就实现所有需求。
在多个迭代里对同一用例进行增量式开发。
细化阶段:构建核心架构,解决高风险元素,定义大部分需求,以及预计总体进度和资源。(包括了三到四次的细化迭代)
关键思想和最佳实践:
实行短时间定量、风险驱动的迭代
及早开始编程
对架构的核心和风险部分进行适应性设计、实现和测试
尽早、频繁、实际的测试
基于来自测试、用户、开发者的反馈进行调整
通过一系列讨论会,详细编写大部分用例和其他需求,每个细化迭代进行一次
制品:领域模型、设计模型、软件架构文档、数据模型、用例、用户界面原型。
领域模型
领域模型是对领域内的概念类或现实世界中对象的可视化表示。
应用UML表示法,领域模型被描述为一组没有定义操作的类图,反映属性和关联。(一定注意,它不是软件对象!是概念对象!)
准则:
使用领域术语
认真汲取领域专家所使用的核心词汇和概念
如果某概念类不是现实世界中的数字或文本,那么她可能是概念类而不是属性
需要描述类:1、需要有关商品或服务的描述,独立于任何商品或服务的现有实例;2、减少冗余或重复信 息
避免加入大量关联,添加关联是为了突出对重要关系的大致理解,而非记录对象或数据的结构
通过关联而不是属性来表示概念类之间的关系,领域模型中属性的类型应该是数据类型(基本类型)
对数量和单位建模
每次迭代的领域建模不超过几个小时。
系统顺序图
系统顺序图(SSD)是为阐述与所讨论系统相关的输入和输出事件而快速、简单地创建的制品。是操作契约和对象设计的输入。
SSD展示了直接与系统交互的外部参与者、系统(作为黑盒)以及由参与者发起的系统事件。这些事件是通过用例文本总结出来的。
应为每一个用例的主成功场景,以及频繁发生的或者复杂的替代场景绘制SSD。
基本上软件系统要对三种事件进行响应:来自于参与者的外部事件,时间事件,错误和异常(通常源于外部)。
系统事件应该在意图的抽象级别而非物理的输入设备级别来表达。
操作契约
操作契约使用前置和后置条件的形式,描述领域模型里对象的详细变化。其中的关键元素是后置条件。
为系统操作定义操作契约。系统操作在绘制SSD草图时确定。
后置条件描述了领域模型内对象状态的变化。可分为三种类型:创建或删除实例,属性值的变化,形成和消除关联。注意,它描述的是所需的变化,而不是这些变化是如何完成的。以说明性的、被动式的过去时态编写后置条件。(例如,创建了xx,而不是创建xx)
逻辑架构和UML包图
逻辑架构是软件类的宏观组织结构,它将软件类组织为包(或命名空间)、子系统和层等。
UML包图常用于描述系统的逻辑架构--层、子系统、包等。
将系统的大型逻辑结构组织为独立的、职责相关的离散层,具有清晰、内聚的关注分离。
协作和耦合是从较高层到较低层进行的,要避免从较低层到较高层的耦合。较低层包含可复用功能。
可以将应用逻辑层更准确地称为架构的领域层,即包含领域对象(注意领域模型与领域对象是不同的两个概念),处理应用逻辑的层。
领域层是软件的一部分,领域模型是概念角度分析的一部分。
从UI层发送到领域层的消息将是SSD中所描述的消息。
轻量级绘图然后编码。
posted @
2007-09-03 17:50 ronghao 阅读(2118) |
评论 (1) |
编辑 收藏