事件驱动的轻量级流程引擎
?
某电子商务交易系统流程较为复杂,具有将近20个流程节点,节点间流转路径较多。
系统 在架构上没有拆分出流程层,处理流程流转的代码和底层的业务组件(如下单组件、发货组件)捆绑在一起,两种流程的代码也搅合在一起,难以灵活快速支撑流程变更,增加新的交易流程也很困难,急需进行改造以提高响应业务方变更流程的速度,提高业务组件的重用性。
?
对Java方面的工作流引擎jbpm和osworkflow,觉得jbpm太重,osworkflow虽然灵活轻量,但对osworkflow和业务组件之间的交互方式不太满意,所以决定自己搞一个轻量的流程引擎,不求有太多的功能,只要够用就行。
?
交易流程
先介绍下系统的交易流程, 下面是做了较多裁剪的简化版交易流程:
?
主干路径为:待下单节点--(买家下单成功后转到) 待付款节点-->(买家付款成功后转到) 待发货节点--> 待确认收货 -->交易结束。
?
待下单节点在实际业务中并不存在,在系统中我们将它建模为开始节点,买家下单为初始事件(操作)。
?
和交易系统进行交互,影响流程流转的涉众(按角色分)主要有买家和卖家、后台客服和超时定时器等(如卖家发货超时定时器,这类用户统一归类为system)。
?
?
改造后的总体架构
?
从上述流程图可以看出,在每个流程节点上可以有一些允许的用户操作,如在待付款节点,可以发生买家付款操作,也可以有支付超时(买家一直未付款, 由超时定时器发起支付超时)、卖家关闭订单或买家取消订单等操作,我们将这些操作称为业务事件。
?
基于SOA架构模式,拆分出业务流程层和业务服务层,采用事件引擎加流程引擎的双引擎架构,来改造我们的交易系统,最终的总体架构如下图。
?
整个系统从宏观上分为业务应用层(为交易前台应用和后台应用)、业务流程层(为交易中心)和业务服务层(省略了基础技术组件层和数据库层)。
?
业务流程层:在原有的系统中新增了交易中心,它作为流程层,部署轻量级流程引擎和流程定义文件。拆分出流程层后,可以灵活地编排流程。
业务服务层:提供业务服务,如下单服务等,这些业务服务可能部署在多个服务器上。
?
在流程层和业务服务层之间有一个集成层,可采用camel实现或ESB,用来调用业务服务和与其他系统交互,在图中未画出。
?
对查询请求,采用CQRS模式将查询和业务事件的处理分开,此点在图中未体现。
?
?
轻量级流程引擎
?
如前所述,轻量级流程引擎由两部分组成:事件引擎和流程引擎。
事件引擎提供事件通道(event channel)来接收业务事件,将业务事件派发给流程引擎中的事件处理器,事件处理器处理完事件后,流程引擎(基于有限状态机FSM)根据定义的流转条件,如根据事件处理器的返回值和或业务事件中的属性值或业务实体中的状态值(事件处理器处理事件后通常会改变某些业务实体的状态)进行流转。对复杂的流转条件可以采用beanshell、groovy等动态语言实现流转条件表达式求值。
进一步说明如下:
买家/卖家在前台应用中进行操作(如买家下单、卖家发货)时,应用层将这些操作封装为对应的业务事件,业务事件中包含有事件类型id、流程实例id、业务数据(如订单信息)等。
事件引擎提供同步事件通道和异步事件通道,事件通道用来接收应用层(如前台应用)或其他系统或组件产生的业务事件。
前台应用将业务事件通过事件通道传给交易中心的事件引擎,事件引擎将事件转发给流程引擎,流程引擎根据业务事件中的流程实例id,找到对应的流程实例和流程的当前节点,将事件再转发给当前节点。我们在每个节点上定义了相应的事件处理器(Event Handler),当前节点收到业务事件后,根据事件中的事件类型id,找到对应的事件处理器,由事件处理器处理业务事件。
事件处理器其实就是业务服务组件的client端,例如对下单事件,由待下单节点中的下单事件处理器进行处理,该事件处理器会调用底层的下单服务创建订单。下单服务创建订单成功后,流程引擎根据该事件处理器的返回值,将流程流转到待支付节点。在其他情况下,流程引擎也可根据实体状态(从数据库表中取状态)来进行流转。
发生节点流转后,流程引擎会发送流程流转转的jms消息,以便通知其他应用发生了流转和状态变化,消息中包含前一节点、流转到的节点等信息。
此外在节点上根据需要可以定义流入操作和流出操作,流入操作是从其他节点流转到该节点时,在该节点上进行的一些操作,如从待支付节点流转到待发货节点时,在待发货节点的流入操作中可以发email提醒卖家及时发货;流出操作是离开该节点时进行的操作。
?
?
流程定义文件
?
流程定义文件通常为xml文件,由流程引擎负责加载并解析。我们的流程定义文件采用spring配置文件定义(当然也是xml格式),只不过其中的流程和节点等都是spring bean,该文件采用定制的spring namespaces。
?
流程定义demo如下(不完整),假设定制的spring名字空间为xpe。
?
<xpe:process name="normal"> <!-- normal流程 -->
?
<!-- 待下单开始节点 -->
<xpe:node name="wait_placeOrder" start=“true”>
?
<!-- 定义事件与事件处理器的对应关系 -->
<xpe:eventhandlers>
?
<xpe:eventMap type="submitOrder" role="buyer" handler="order::createOrder"/> <!-- 定义下单的事件处理 器,type为事件类型id,role标示只有买家才允许操作,handler为事件处理器,其中order为下单服务客户端组件的spring bean id,createOrder为方法名-->
?
<xpe:transition condition="#{return.reslut}==true"> <!-- 定义流转条件表达式,支持and、or等逻辑运算,此处根据返回值dto中的result属性为true时进行流转,实际写为#{return.result}即可-->
<xpe:to>wait_payment</xpe:to> <!-- 满足流转条件时,将要流转到待付款节点 wait_payment -->
</xpe:transition>
?
</xpe:eventMap>
?
</xpe:eventhandlers>
?
<!--定义页面url -->
<xpe:url role="buyer" target="placeorder.htm" /> <!-- 买家在该节点使用的url页面,此处为下单页面 -->
?
</xpe:node>
?
?
<!-- 待付款节点 -->
<xpe:node name="wait_payment"> <!-- 待付款节点 -->
...
</xpe:node>
</xpe:process>
?
?
?
?
?
?
?
?
?
?
?
?
?