基于Petri网的工作流分析和移植
一、前言
在实际应用场景,包括PEC的订单流程从下订单到订单派送一直到订单完成都是按照一系列预先规定好的工作流策略进行的。
通常情况下如果是采用面向过程的编程方法,我们采用的方式无非就是判断当前的工作流状态以及操作步骤来选择工作流分支继续下一步,如果整个工作流从起始到结束所执行的步骤不多的话,采用此方式相当简便,但如果步骤一多起来,或者分支太多以及需要判断的或者切换的状态太多的时候,很容易出错,或者说在原有的工作流分支上新增一个操作步骤,则改起代码来会非常繁琐且易出错。比如在WMS系统个人做的售后退货流程的状态切换代码。
参考了PEC的订单流程,采用的是基于活动的工作流机制,实际核心是基于Petri网理论。从系统的构架设计上做了多层体系分离,工作流系统和业务系统之间具有很好的松散性。从而达到工作流系统不需要知道业务系统,业务系统也不需要了解工作流。
二、Petri网介绍
摘录自百度百科的Petri网介绍。Petri网可以描述每一个节点拥有自己的独立时序,只要条件满足,就可以发生。Petri网旨在描述变迁之间的因果关系,并由此构造时序。经典的Petri网是简单的过程模型,由两种节点:库所和变迁,有向弧,以及令牌等元素组成的。
结构
Petri网的元素:
+ 库所(Place)圆形节点
+ 变迁(Transition)方形节点
+ 有向弧(Connection)是库所和变迁之间的有向弧
+ 令牌(Token)是库所中的动态对象,可以从一个库所移动到另一个库所。
规则
+ 有向弧是有方向的
+ 两个库所或变迁之间不允许有弧
+ 库所可以拥有任意数量的令牌
o 行为
如果一个变迁的每个输入库所(input place)都拥有令牌,该变迁即为被允许(enable)。一个变迁被允许时,变迁将发生(fire),输入库所(input place)的令牌被消耗,同时为输出库所(output place)产生令牌。
三、CI框架上的移植
从上述的举例,售后退款流程也可以采用基于Petri网的工作流模型,一方面可以使工作流系统和业务流系统分隔开,开发人员按照工作流策略设计数据表的库所和变迁以及有向弧,另一方面每个库所或者变迁都可以采用面向对象的方式继承相应的方法从而达到松耦合。
数据表结构主要有Arcs、Cases、Places、Tokens、Transitions、Workflows和WorkItems。
Arcs:用于描述库所和变迁之间的迁移;
CREATE TABLE `Arcs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `placeId` int(10) unsigned NOT NULL COMMENT '库所id', `transitionId` int(10) unsigned NOT NULL COMMENT '变迁id', `isP2T` tinyint(3) unsigned NOT NULL COMMENT '是否库所到变迁:0否1是', `preCondition` varchar(20) NOT NULL COMMENT '变迁发射后,输出库所获得令牌的条件', PRIMARY KEY (`id`), KEY `placeId` (`placeId`), KEY `transitionId` (`transitionId`) ) ENGINE=MyISAM AUTO_INCREMENT=407 DEFAULT CHARSET=utf8 COMMENT='向弧'
Cases:用于创建工作流实例;
CREATE TABLE `Cases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `siteId` int(10) unsigned NOT NULL, `workflowId` int(10) unsigned NOT NULL COMMENT '工作流id', `caseStatus` varchar(10) NOT NULL COMMENT '状态', `startTime` int(10) unsigned NOT NULL, `endTime` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `workflowId` (`workflowId`), KEY `siteId` (`siteId`) ) ENGINE=MyISAM AUTO_INCREMENT=66605 DEFAULT CHARSET=utf8 COMMENT='工作流实例'
Places:用于描述库所,主要有开始库所、中间库所以及结束库所;
CREATE TABLE `Places` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `workflowId` int(10) unsigned NOT NULL COMMENT '工作流id', `placeType` tinyint(3) unsigned NOT NULL COMMENT '库所类型:1 开始库所 5 中间库所 9 结束库所', `placeName` varchar(100) NOT NULL, PRIMARY KEY (`id`), KEY `workflowId` (`workflowId`) ) ENGINE=MyISAM AUTO_INCREMENT=64 DEFAULT CHARSET=utf8 COMMENT='库所'
Tokens:用于描述令牌,状态分别有*、锁定、消耗以及取消,同时包含令牌出现时间、取消时间和消耗时间;
CREATE TABLE `Tokens` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `caseId` int(10) unsigned NOT NULL COMMENT '实例id', `placeId` int(10) unsigned NOT NULL, `tokenStatus` tinyint(3) unsigned NOT NULL COMMENT '状态:1 * 2 锁定 3 消耗 4 取消', `enabledTime` int(10) unsigned NOT NULL COMMENT '令牌出现时间', `cancelledTime` int(10) unsigned NOT NULL COMMENT '令牌取消时间', `consumedTime` int(10) unsigned NOT NULL COMMENT '令牌消耗时间', PRIMARY KEY (`id`), UNIQUE KEY `casePlace` (`caseId`,`placeId`) ) ENGINE=MyISAM AUTO_INCREMENT=235153 DEFAULT CHARSET=utf8 COMMENT='令牌'
Transitions:用于描述变迁,变迁的触发方式有用户手动触发、系统自动触发、外部事件触发以及到期触发几种策略;
CREATE TABLE `Transitions` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `workflowId` int(10) unsigned NOT NULL COMMENT '工作流id', `transitionName` varchar(30) NOT NULL, `transitionDesc` varchar(60) NOT NULL, `transitionTrigger` tinyint(3) unsigned NOT NULL COMMENT '变迁如何被触发:1 用户手动触发 2 系统自动触发 3 外部事件触发 4 到期触发', `timeLimit` int(10) unsigned NOT NULL COMMENT '当transitionTrigger为4(到期触发)时有效,表示多少小时触发', `taskId` varchar(255) NOT NULL COMMENT '触发时哪个任务被激活', `identify` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `workflowId` (`workflowId`) ) ENGINE=MyISAM AUTO_INCREMENT=97 DEFAULT CHARSET=utf8 COMMENT='变迁'
Workflows:用于描述工作流;
CREATE TABLE `Workflows` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `workflowName` varchar(100) NOT NULL, `startTaskId` varchar(60) NOT NULL, `workflowType` varchar(20) NOT NULL COMMENT '流程类型', `siteIds` varchar(255) NOT NULL COMMENT '使用此流程的站点id列表,逗号隔开,前后有逗号(default表示默认)', PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=37 DEFAULT CHARSET=utf8 COMMENT='工作流'
WorkItems:用于描述工作项。
CREATE TABLE `WorkItems` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `caseId` int(10) unsigned NOT NULL, `transitionId` int(10) unsigned NOT NULL, `transitionTrigger` tinyint(3) unsigned NOT NULL COMMENT '如何触发:1 用户触发 2 系统触发 3 外部事件触发 4 超过时间触发', `taskId` varchar(255) NOT NULL, `workItemStatus` tinyint(3) unsigned NOT NULL COMMENT '工作项状态:1 启用 2 处理中 3 取消 4 完成', `enabledTime` int(10) unsigned NOT NULL, `cancelledTime` int(10) unsigned NOT NULL, `finishedTime` int(10) unsigned NOT NULL, `deadline` int(10) unsigned NOT NULL, PRIMARY KEY (`id`), KEY `caseTranId` (`caseId`,`transitionId`) ) ENGINE=MyISAM AUTO_INCREMENT=626106 DEFAULT CHARSET=utf8 COMMENT='工作项'
基于Petri网的工作流引擎主要可使用的方法有启动工作流、取操作模板、执行指定ID的操作、取用户可执行的操作列表、取任务类、运行任务、取工作流权限列表、完成超时工作项、完成工作项等操作。库所和变迁类所需要继承的接口分别是获取操作模板以及启动任务方法。
四、下一步方向
1) 在基于Petri网的工作流模型中,有可能会出现用户误操作,需要回滚的,目前在PEC订单流程中偶尔会出现误操作,需要开发人员通过操作数据库来回滚数据,后续可以考虑工作流回滚方式。
2) CI框架采用的都是基于model模型操作,而PEC是采用数据实体类操作的,是把数据库数据存取到指定数据实体类,再统一写入数据库,同时在在级联操作上也很方便,后续CI可以考虑新增数据实体类。