NetBPM工作流的架构设计及实现浅析 |
目录NetBPM组件接口 定义组件(Definition Component) 运行组件(Execution Component) 组织架构组件(Oorganization Component) 日志组件(Log Component) 任务调度组件(Scheduler Component) 管理监控组件(Admin Component) NetBpm中的几个重要概念 flow attributes(属性) 引擎运行时上下文环境(Execution Context) 委托类 流程定义版本问题 异常处理机制 流程定义元素类图 NetBpm中使用的框架或组件 IOC容器――Castle 数据持久层―― NHibernate 示例Web层――MonoRail 系统日志 ――Log4Net 单元测试工具――NUnit 后记 读前的话:由于本文涉及内容颇多,若有地方读来不很明白,建议先跳过,整体上有个认识后,再回过头来理解。作者认识有限,若有错误,欢迎斧正:)原文地址: NetBPM工作流的架构设计及实现浅析(转载请保留) NetBPM组件接口 NetBPM由一系列的组件构成,每一个组件都实现一个核心接口(采用Facade Pattern)。不同组件各自负责的核心功能根据WfMC规范而来。
下面我们逐步介绍NetBPM的各个组件,下面是NetBpm组件结构图: 定义组件(Definition Component)该组件实现核心接口IDefinitionSessionLocal,用来解析、加载流程定义压缩包,并将其保存到数据库中。此外,它还提供获取某个流程定义,获取所有有效流程定义列表等和流程定义相关的方法。 运转组件(Execution Component)该组件实现了核心接口IExecutionSessionLocal,它是NetBPM引擎推动核心,如前面如述,它主要实现2个方法:开始一个流程实例(Start ProcessInstance)和执行一个活动(Perform Activity)。另外,它还提供获取用户任务列表,取消流程实例等辅助流程运转的方法。 组织架构组件(Organization Component)该组件实现接口IOrganisationSessionLocal,它把所有的组织架构信息都聚集在一起,包括Users、Groups和Memberships。运转组件在为activitie-state指定执行者时,需要有关user和group的信息。这些信息将以只读模式由组织架构组件向运转组件提供。下面是NetBPM默认的组织架构组织数据模型。在此基础上,我们可以很方便的实现自定义的适合实际项目需求的组织架构组件,以和我们的用户数据库或者是LDAP系统相关联。 NOTE:NetBPM源码中实现的只是一个简单的组织架构模型,但它提供了一种思考方向,我们可以很方便在此基础上进行扩展来实现满足切实需求的自定义组织架构组件:) 日志组件(Log Component)该组件实现接口ILogSessionLocal,用来记录工作流引擎的操作痕迹,象属性值的更新(如用户提交的表单被上级打回重新填写,那么就会出现多次表单数据,这就是一种属性更新),委托类的调用情况等都会被记录下来保存到数据库中。 任务调度组件(scheduler component)该组件实现接口ISchedulerSessionLocal,在现实的业务流程中,我们经常会遇到需要定时触发某个任务的需求,任务调度组件正是作用于此。引擎或者是第三方把需要在某个时刻执行的任务信息(包括任务执行环境、执行时间等)封装成Job保存到数据库中。任务调度组件将按照指定的时间间隔不停的扫描任务表,根据执行时间对比来执行定时到了的Job。 管理监控组件(Admin Component)该组件用来对流程定义,流程实例执行情况等进行监控。(源码待完善)下面是NetBpm核心项目在Visual 该组件用来对流程定义,流程实例执行情况等进行监控。(源码待完善) 下面是NetBPM核心项目在Visual Studio解决方案中的源码结构图,所有组件都包含在Workflow文件夹下,每一个文件夹分别对应实现了一个核心组件。 NetBpm中的几个重要概念flow flow不知道翻译为什么好,在JBPM中叫做Token,翻译为令牌,这里我们就叫做flow吧。它代表activity-states(活动节点,见nPdl)的一个“thread of execution”,相当于是一次流程实例执行过程中在流程定义模板中的令牌(还真难描述清楚,看下面一起理解:))。前面说了,一个流程实例代表一个流程定义的一次执行。如下图所示,流程实例的状态可以看成是一颗flows树。 当开始一个流程实例后,在start-state(开始节点,见nPdl,start-state实际上可以看做是一种特殊的activity-state)引擎将自动产生一个名为root的flow。flow中包含了该流程实例的相关信息,如属性值、流程定义信息、flow所在activity-state的执行者等。root flow在遇到fork(分散节点,见nPdl)之前,将更新其带有的实时信息(如执行者、属性值等),这些实时信息随着流程的运转而变化。遇到fork后,根据ForkHandler委托类,root flow将分散成若干(>1个)forked flow(我们可以把root flow称为这些forked flow的父flow)。若是分散为多个,则此时forked flow将并行运行,父flow则暂时退隐,只至到join(汇聚节点,见nPdl)汇聚,引擎将根据join定义的JoinHandler委托类来确定激活父flow的机制。 NOTE:关于fork和join机制,请参考nPdl fork、join小节一起理解。 attributes(属性)attribute用来表示流程实例中的变量。一个attribute-instance(属性实例,也就是属性值)代表一次流程实例执行过程中对应属性的实例。属性一般有几种,一种是activity-state(包括start-state)需要用户或者第三方来填写(更新)的属性(一般对应用户Web界面表单上要填写的值),一种是角色对应的属性,还有一些用作标识属性(用来存储某些信息以方便后面的节点运用这些信息处理逻辑判断)。
引擎运行时上下文环境(Execution Context) 因为图片太大,关于继承IHandlerContext的接口关系图查看点击这里 ExecutionContext(ExecutionContextImpl类型的对象,我们暂且翻译为运行时上下文环境:)) 包含了引擎在运行时和流程实例相关的所有有用信息(上文中提到的flowcontext就是一种ExecutionContext),它在委托类(包括流程定义压缩包中程序集中定义的委托类)和流程引擎之间建立起了相互联系的渠道,这是非常关键的。如上面ExectutionContext 类图所示,ExecutionContextImpl实现了下面这些接口:IAssignmentContext、IDecisionContext、IForkContext、IActionContext、IJoinContext、IProcessInvocationContext、ITaskContext,这些接口都是为匹配特定的委托类而设计,它们规范了一种特定的上下文环境,如IActionContext匹配action类型委托类,IDecisionContext匹配DecisionHandler类型委托类等,而ExectutionContext是所有这些特殊的运行时上下文环境的一个综合。当引擎在运转组件把ExectutionContext作为参数传送到具体类型的委托类时(关键:这就是委托类和流程引擎建立联系的方式),ExecutionContext对象将“拆箱”成为特殊的Context,如:把ExecutionContext对象传给action类型的委托类Run()方法时,ExecutionContext对象将拆箱为ActionContext对象以限制其能够调用的方法。 如“继承自IHandlerContext的接口”图中所示,这些接口都继承了同一个接口IHandlerContext。IHandlerContext是一个规范了最基本的委托类处理上下文环境的接口,实现该接口的继承几口也就都要实现IHandlerContext中定义的方法,当然每一种继承它的特定接口又都可以具有其特定的方法。我们先看公共接口IHanlderContext类图:如上IHandlerContext接口图所示,这些接口都继承了同一个接口IHandlerContext。IHandlerContext是一个规范了最基本的委托类处理上下文环境的接口,实现该接口继承接口的类也就都实现了IHandlerContext中定义的方法。当然,每一种继承它的特定接口又都可以具有其特定的方法。我们先看公共接口IHanlderContext类图: 大多数的方法,我们从方法名称就可以看出其具体作用了,这里重点介绍下GetAttribute()方法和GetConfiguration()方法,这是我们在写委托类实现时,要经常用到的2个方法。GetAttribute()用来获取流程实例中的属性值,包括流程前面处理者产生的属性值(如用户填写表单的值)和前面处理引擎事件中设置的表示属性值(注:IActionHandler具有SetAttribute()方法,该方法经常用来标识属性值,供后面程序逻辑用)等。而GetConfiguration()用来获取流程定义中设置的parameter(参数,请nPdlparameter小节)。 下面再来看几种典型的特定上下文环境接口:
委托类在前面我们一直提到委托类,那么委托类到底是什么呢?这里委托的概念指的不是.NET Framework中delegate,这里可以理解它为“委托、代为处理”这样的概念就好。 NetBPM被设计成通用的流程处理引擎,NetBPM核心执行引擎只负责处理最基本的业务流程逻辑,所有不定的逻辑都被委托给一系列的接口,这些接口称作Delegation Interfaces(委托接口),而实现这些接口的类就是委托类。流程定义约定在什么场合使用什么委托类型,引擎和委托类如何关联也在流程定义中完成。 为了达到最大的可扩展性,流程开发者在流程定义时可以选择下面任意一种委托类实现方式:
正是方式2这种形式给NetBPM带来了极大的灵活性,把只适合于某个特定流程的的程序逻辑(这些往往占了大多数)以.NET程序集的形式定义在流程定义压缩包中,NetBpm通过流程定义组件将其解析并保存至数据库。当引擎运转流程需要调用委托类时,引擎利用反射技术实例化出委托类对象,然后利用上文介绍的运行时上下时环境(ExecutionContext)建立起委托类和引擎之间的交互渠道,这真是一个令人兴奋的设计:) 下面是NetBpm中的委托类型(建议和ExecutionContext一节一起理解):
................///.............是否要添加委托类的例子 流程定义版本问题流程定义的名称与版本包含在一个流程定义压缩包中的信息叫做流程定义。NetBpm中,流程是由字段name来区分的,也就是说引擎根据流程的name来判断两个流程是否相等。在流程定义包中不能指定版本,当一个流程定义被引擎加载后,NetBpm将检查是否有该流程定义的旧版本。如果有,NetBpm将自动设置该新加载进来的流程版本为所有存在的旧版本流程定义中最高版本数目基础上加1。 流程执行与版本当调用运转组件获取流程定义列表方法时,只能获取到每个流程的最高版本流程定义。这样做保证了用户总是从最新版本的流程定义开始一个流程实例。当新的流程版本加载到NetBpm时,所有正在运行的旧版本的流程实例将保持在原来流程定义方式下运行。 委托类与版本 关于版本的另外一个方面是委托类。不同版本流程定义的委托类不是共享的,也就是说每个流程在执行时只会“看到”它自己流程定义的委托类。 异常处理机制NetBpm作为一个集成平台,当流程运行时,肯定会依赖公司很多其他的IT资源,一旦这些依赖导致流程执行时出现错误,NetBpm提供了3种解决机制:
执行回滚机制中,流程实例将会被回滚到执行activity之前的状态。如果是对NetBpm 调用Eecution Interface时发生流程错误,所有的流过的transition(边)都会执行回滚。 流程定义元素类图 关于流程定义的详细情况,参见nPdl。
NetBpm中使用的框架或组件NetBpm中用到的框架、组件、工具比较多,它们大都是优秀的开源项目。如Castle,NHibernate,Log4Net, NVelocity,NUnit,NAnt等,不要被这些框架吓倒,实际上,它们仅仅只是“框架”而已:) IOC容器――CastleNetBpm使用了Castle框架,主要用它来实现IOC(控制反转或者说依赖注入),以依赖注入的方式加载核心组件,如DBClassLoader,流程定义组件,运行组件,日志组件,组织架构组件,任务调度组件等。在Web程序启动的时候,根据配置文件,所有的核心组件都将注册到Caslte IOC容器中,以后当需要使用某个组件的时候,只需利用系统提供的ServiceLocator(服务加载工具类)从容器中获取实例即可。另外,在任务调度组件中也有用到Castle的Startable Facility(注:大家把facility理解为注入性质的,对Castle IOC内核容器的功能扩充组件。Castle本身自带实现了一些faciltiy,开发者也可以自定义facility),该facitlity主要用来自动运行程序(这里用来自动间隔扫描任务表,进行任务调度)。 Castle是.NET平台下一个功能强大的优秀开源框架,关于Castle的更多信息,请看Castle官方网站。另外TerryLee的博客中关于Castle的中文资源也很丰富。 数据持久层―― NHibernateNetBpm中NHibernate组件是作为Castle的一个facility存在的,它用来实现NetBpm数据持久层, 并方便的实现了事务支持。关于NHibernate的更多信息,请看NHibernate官方网站,关于NHibernate作为Castle的facility相关请看这里。 示例web层――MonoRail大家在Demo演示体验的时候,一定很奇怪,没有看到熟悉的.aspx页面,而是.rails页面,为什么呢?答案就是NetBPM的Web层采用的是MonoRail框架,而不是我们熟悉ASP.NET框架。MonoRail是Caslte框架下针对web层编程的一个子框架,它从Ruby Rails获取灵感而来,采用架构清晰分工明确的MVC模式。NetBPM采用的使用NVelocity作为页面解析引擎的MonoRail,它只是对NetBPM核心API的一个Web界面演示示例,用来告诉我们该怎样从Web层调用NetBpm API。所以,虽然MonoRail有其独到之处,但是在其被普及并有好用工具支持之前,我们只需简单了解下它的运行机制,用以熟悉web层如何利用NetBpm API,不需要了解它太多。用我们熟悉的ASP.NET实现Web部分显然是更好的选择:)。关于MonoRail的更多信息,请看这里。 系统日志 ――Log4Netlog4net大家一定不陌生了,NetBpm使用它来记录系统日志,关于log4net的更多信息,请看Log4Net官方网站,网上中文资源也很丰富. 单元测试工具――NUnit单元测试工具NUnit一直是大家用来单元测试的利器。 NetBPM源码中已经建有几个测试工程。关于NUnit的更多信息,请看NUnit官方网站。 注:移植到.NET Framework 2.0下,可能要更改其版本。 待写:NetBPM工作流nPdl详解,一个NetBPM现实生活中请假审批示例 后记 NetBPM的设计无疑是巧妙的,但是现阶段的它显然还不是一个完美的工作流引擎,缺乏如JBOSS这样的强大后盾作支持,中途又遇上强敌WF,NetBPM远没有其兄弟JBPM风光,更新没有它快(JBPM已经出3.0版本了),获取的支持也少许多, 但是.NET平台下能有这样一个优秀的开源工作流项目是十分可贵的,如果您正在WF中苦苦挣扎,也许,开源的NetBPM将带给您一个惊喜:) |