E-State和工作流
我在前面的文章“状态机与工作流(State Machines and Workflow)”(WLDJ,卷3,第1期)中讨论过工作流和状态机,它们是面向流程的应用程序的补充实现策略。 状态技术是对许多业务流程中所采用的“里程碑”技术所做继承的强大抽象。另一方面,工作流 - 这里特指BEA Weblogic Integration的Business Process Modeler(BPM)组件 - 提供了重要的企业级服务,例如外部系统集成、人员工作列表(worklist)和任务管理,事件和计时器,以及XML消息处理。在前面的文章中指出过,混合的状态-工作流解决方案有以下几个部分:
1. 状态机框架,由以下部分构成:
- 状态模型: 一套状态和转换,在XML文档中表达。
- Actor数据库: 主角(actor)是指具有状态的实体。Actor的状态由状态机保存在数据库里。
- 状态机引擎: 把事件插入Actor的状态模型中,并相应地更新状态。它还会在进入、退出状态或者发生转换时,调用用户自定义动作类。
- 动作类: 用户自定义的Java类,负责响应某种状态的进入或退出,或者某个Actor在特定状态模型下执行过滤形态。
2. BPM工作流:负责接收事件,然后把事件插入状态机。
3. BPM工作流:设置计时器,在时间用尽的时候把超时事件插入状态机。
4. BPM工作流:某一状态动作调用该工作流,给它分配一个工作列表任务或与外部系统交互。
E-State 是第一部分即状态机框架的参考实现。本文来讨论状态机的体系结构。
E-State 体系结构
方法学
E-State的状态模型基于实时面向对象建模(Real-time Object-Oriented Modeling -ROOM) 的方法。ROOM 的状态图是层次结构的,也就是说每个状态可以拥有子状态。这个主意很简单,效果却异常强大。 从人类思维的角度来看,平面状态模型无法进行扩展。当状态和转换的数量增长时,平面模型就会变得难于理解。而层次状态模型则可以分部分考察,每一部分理解起来相对就变得简单了。例如,考虑图 1。
在这个模型里,转换 ab 会使状态 a1 或 a2 变成状态 b; 而转换ac 会使状态从 a1 或 a2 变成 c。所有状态的转换toC 会使状态变成c。 在状态 a1里的转换 ba 会形成新的状态 a。初始状态是 b。状态从 a1变为a2时,要经过转换 a1a2,从 a2 变为a1时,要经过a2a1。转换ca把状态引到一个选择点:如果最后的状态是a1,就变成a1,否则就变成 a2。
同样的场景,用层次结构来表示,理解起来就容易多了,如图2所示:
首先,超级状态 a被细分为状态a1 和 a2(图2中的右图);这样整个系统的状态图(图2中的左图)变得更简洁。从状态的高最层来看,转换ab 和 ba只是在状态a 和 b之间转换;但是在状态 a里,可以看到 ba 指向子状态 a1,而 ab则来源于a1。 转换ac 从 a1 或 a2 开始,指向 c;转换 ca 从c开始,指向 状态a的最后一个子状态。另外,转换toC不象图1中那样,要从每个状态来开始;来自最高层状态的非扩展转换点toC 的事件指向状态 c,就可以表示需要的行为。
作为层次结构设计的成果之一,ROOM提供了二个强大的特性:组转换和历史恢复。所谓组转换是指:针对指定状态发生的转换,不论指定状态处在什么子状态当中;转换 ac 把状态从 a 变为 c,不论状态a的子状态是a1还是 a2。历史恢复 就是变回指定状态最近的子状态;转换 ca 把状态 c 变回 a最近的子状态.
可以选择的方法还有UML和Petri-nets,它们都支持层次结构。
引擎
E-State的核心是一个无状态的会话Enterprise JavaBean (EJB),它被称为状态机(StateMachine),如图3中的阴影部分所示。
StateMachine EJB被配置成指向具体的状态模型,使用XML文件来进行配置,配置文件中包含以下内容:
·一组状态和一组转换;
·唯一的命名空间,唯一命名空间有助于多重部署,稍后介绍。
·Java “动作”回调类的名称,状态机处理事件时,调用回调类。
EJB把模型用于“Actor”。在ROOM方法中,Actor指的是一个“活动”对象,状态模型最好地描述了这个对象的行为。
(在 ROOM里,活动对象拥有自己的控制线程,以及一组自己的入站、出站消息接口。E-State里Actor的概念更严格) 在 E-State里,Actor是拥有状态的实体,例如一个保险索赔。在一个模型里,从一个状态到另一个状态的转换,反映了Actor的状态变化;例如,索赔可能处在等待状态,激活状态,或者空闲状态。 E-State 有三个表负责跟踪Actor的状态,这三个表是 Actor(主角)、Actor_Property(主角属性)和 Actor_State(主角状态),还有对应的实体 EJB (Actor,ActorProperty,和 ActorState) 来表示这三个表,如图3所示。StateMachine EJB 某种程度上可以看作这些实体EJB的一个层面(facade)。StateMachine EJB的Actor管理方法有: createActor(),getCurrentState(),getChildState(), getActorProperty(),getActorProperties() 和 setActorProperty()。
状态机余下的方法 (startMachine() 和 injectEvent()) 形成了状态机引擎,驱动着Actor的状态变化。实际上,startMachine()只是调用 injectEvent(),给它传递了一个特殊的“初始化”事件,由injectEvent()执行转换操作,转换操作的起点是模型中每个状态的初始转换点。所以, injectEvent() 方法是状态机的核心,由它来驱动业务流程的动作前进。这个方法可以调用用户自定义动作类,从而实现模型中所定义的状态行动(StateAction)接口。动作类的功能是将重要的状态机事件通知客户,并向客户请示逻辑决策。在表1里列出了动作类的方法。
表1 动作类的方法
方法 | 动作 |
OnStateEnter | 通知进入了一个状态 |
OnStateExit | 通知退出了一个状态 |
OnTransitionExecute | 通知执行了一个转换。如果方法返回真,则允许该转换发生,如果为假,则阻止转换(在ROOM的概念里,称为警卫(guard)) |
Choice | 要执行选择点决策的请求。返回值为真或假,控制着状态模型里控制分支的流转方向。 |
在保险索赔的例子里,动作类启动工作流,执行与任务相关的工作或者清理工作,或者启动计数器。在 WebLogic Integration 7.0里,由BPM 的API启动工作流。在In WebLogic Integration 8.1里,则用Web服务调用工作流。
表2里归纳了StateMachine EJB的方法。
表2 StateMachine EJB的方法
方法 | 动作 |
CreateActor | 在Actor表中为StateMachine EJB代表的模型建立一个新记录 |
GetCurrentState | 取得StateMachine EJB代表的模型的Actor的当前叶子状态 |
GetChildState | 取得StateMachine EJB代表的模型的Actor的指定状态的当前子状态 |
GetActorProperty | 取得StateMachine EJB代表的模型的Actor的指定属性值 |
SetActorProperty | 设置StateMachine EJB代表的模型的Actor的指定属性值 |
GetActorProperties | 取得StateMachine EJB代表的模型的Actor的名称、类型和每个属性的值 |
StartMachine | 执行模型里的每个状态的初始化转换,启动StateMachine EJB代表的模型的Actor的状态模型 |
InjectEvent | 把指定事件插入StateMachine EJB代表的模型的Actor的状态模型里 |
在保险索赔的例子里,插入器(Injector)工作流调用状态机的 injectEvent()方法。在WebLogic Integration 7.0里,工作流使用一个业务操作来调用这个方法,而在 WebLogic Integration 8.1,则用EJB控件来完成。
数据库架构
图4显示了保持Actor持久状态信息的表结构。.
在主表 Actor里,保存了特定 模型类型的Actor的当前状态。当前状态是指Actor目前所处的叶子状态。这个表的主键由Actor的唯一标识符和它的模型命名空间组合而成的。一个Actor可能在多个 模型命名空间里具有状态。特别的是,如果在不同的命名空间里存在着同一模型的二个版本,那么在每个命名空间里的Actor状态都能在Actor表里表示。
Actor_State表捕捉特定命名空间里的特定Actor的复合状态的活动子状态。这个表仅供内部使用,状态引擎用来来实现历史恢复。在actor和actor_state表之间存在着一对多的关系。
Actor_Property 表保存特定命名空间里的特定Actor的用户自定义属性。每个属性都有一个名字(对于每个命名空间的每个Actor,名字必须是唯一的),一个类型,和一个值。这个表为客户应用程序提供了方便,可以把一组数据与角色关联;更常见的情况是,应用程序的数据保存在应用程序的数据存储机制里。
部署
StateMachine EJB会为每个状态模型部署一个不同的实例。每个实例的源代码是相同的(相同的home和 remote接口,相同的实现),但是具体的配置不同。StateMachine EJB的部署描述符指定了唯一的JNDI(Java命名和目录接口)名(客户用这个名字来定位EJB),还有一个对模型XML文件的引用。例如,保险状态模型的StateMachine EJB可能有一个 JNDI 名"state_insurance" ,并指向文件 "Insurance.xml"。要与这个模型交互,客户应用程序可以用"state-insurance"这个JNDI名来访问模型的EJB并调用EJB的方法。这个特殊的方法有着显著的优势:
·生命周期:要想准备好一个可供处理的新状态模型,需要部署一个指定该模型的StateMachine EJB。要想取消这个模型,需要取消EJB的部署。要把变化交给模型,需要用修改过的模型文件重新部署EJB。
·版本管理:如果现有的状态模型有一个新的主版本,那么新版本可以部署成独立的EJB,与以前的版本并存。
例如,“state-insurance-1.1”可以与“state-insurance”并存。
数据模型同样支持版本管理。给定的Actor有多个模型的持久状态,包括相同模型的不同版本,只有模型有不同的名称。.
大多数业务流程要运行相当长的时候,所以应用程序升级的管理变得极富挑战。有二个场景很难解决:
1.做了一个小补丁,但是有Actor正在用没有打补丁的版本运行着。
2. 做了一个主要补丁,只有新Actor可用,旧的Actor仍然使用以前的版。.
E-State 是解决这些问题聪明的解决方案:
1.应用小补丁,意味着为模型重新部署现有EJB。Actor会在停止的地方重新开始,补丁同时发挥作用。.
2.应用主要补丁,意味着用一个独立的命名空间部署新的EJB,而现在已经部署的EJB保持不变。模型彼此独立,这样老Actor用旧版本运行,新Actor用新版本运行。
结束语
E-State是由ROOM方法所启发的一个企业级状态机框架。它与BMP工作流集成在一起,提供了关键的集成服务,例如系统集成、事件、计时器、工作列表以及XML。这为开发面向流程的业务应用程序提供了强大的解决方案。E-State中包括:运行时引擎,状态模型架构,持久性服务,用户自定义“动作”类(在发生转换时,在进入或退出状态时,引擎会调用用户自定义“动作”类。)动作类调用工作流来利用BPM服务;而工作流被事件触发时,则调用引擎来触发转换。
参考资料
· Selic, Gullickson, and Ward. (1994). Real-Time Object-Oriented Modeling. Wiley.