流程前事件执行但流程未提交问题

# 1、问题背景

目前发现有一类客户问题,与第三方系统对接相关,在节点上配置了执行前事件,该事件的业务职能,往往需要向第三方系统有交互。而这类问题,有如下现象: a.该节点同时也配置有自动跳过相关功能,自动跳过失败转人工处理,人工提交后,发现三方系统的事件监听功能执行了两次,导致三方系统数据异常 b.用户使用批处理功能提交流程,发现提交失败后手动提交,但手动提交时被开放给第三方的提交前事件实现拦截功能,后续再也无法提交流程 以上两个场景抛出了多个维度的问题,但总结就是 1、如果OA服务提交有问题,第三方怎么办? 2、如果第三方出现异常,OA服务流程该怎么办?

# 1.1 流程前事件设计

从业务上看,以提交前事件为例,顾名思义,流程提交前的事件一定发生在提交事务过程之前。如果客户需要在前事件里实现业务,然后要求系统一定能完美正常运行前事件之后的动作,是不可能的,而且必须要把异常作为常态化场景设计与开发。提交异常不一定说是程序异常,也有可能是设计上的可预期的异常,比如在自动跳过时存在需要选人或者选分支的场景,后台线程在此时同样无法处理。所以不能不面对以上两个问题。

# 1.2 流程事件种类

目前BPM引擎在底层的运行时支持以下事件类型

事件 事件级别
流程发起前 流程级
流程发起 流程级
流程终止前 流程级
流程终止 流程级
流程结束前 流程级
流程结束 流程级
流程撤销前 流程级
流程撤销 流程级
提交前 事项级
提交 事项级
回退前 事项级
回退 事项级
取回前 事项级
取回 事项级

除了发起相关事件外,其他事件的触发都可以找到OA系统内部流程实例,可能都对应着三方的业务数据;所以发起事件需要根据业务数据内容特殊处理

而其他可以找到OA系统内部流程实例的流程事件,都需要做相关考量,只是按目前经验分析,【提交前事件】问题比较突出,但是理论上其他前事件类型,是具有相同的问题

# 1.3 问题分析

对于同一条流程而言,不论是流程级还是事项级事件,都需要从业务上考虑是否可以执行多次。在某些场景下执行多次也存在合理性,但在另外的场景下明显又不合理,一旦发生不合理的场景,造成问题的本质都是 一致性 问题。前文已经提到过,某些造成不一致的原因无法避免,但还是可以通过技术手段规避绝大部分的一致性问题。即便可以执行多次的场景,理论上也需要通过技术手段,验明多次执行的流程事件,是否属于业务上的多次执行,而非由于异常的多次执行。

只是 当前的不一致是来源于我们OA系统与第三方系统的不一致此时必须由两个系统的连接点,由CIP的业务实现,(实现可能来自于客开业务代码本身),提供更好更全面的实现来解决上述一致性问题。

# 2、解决方案及原理说明

# 2.1 解决方案

经过多方面的技术方案考量,最终方案是『要求事件实现方接口实现幂等』, 幂等的数学表达式是

幂等的业务释义是 将事件触发使用的参数作为接口参数,只要使用同一个参数,那么无论调用多少次接口,结果都是一样的。 也就是说只要对同一参数做过映射,要求实现调用一次和调用多次最终的效果一致。

# 2.2 方案如何解决问题

我们回过去看一下背景中的两类问题 a、如果执行了前事件,但提交时异常 b、如果三方事件执行失败,但流程已提交 对于a类问题,幂等接口天然就要求同一事件能重复执行。对于实现方来讲,关键就在于我只需要确保事件能成功执行一次就可以,如果存在多次的调用,不能让事件实现执行多次,否则反而对业务会有影响, 对于b类问题,如果成功调用了外部事件实现,不管有没有执行成功,三方对接的系统其实就拥有让这个事件能正确被处理的条件。即便失败了,除非面对面对不可抗力的异常,存在很多策略可以让动作重新执行,直到成功。

因此,综上额外投入对事件执行管理是安全且有必要的。 实现方案就必须具备几个要素 a、业务需要一个唯一标识区分业务任务 b、独占式的去执行同一件事件任务 c、事件任务成功执行后更新执行结果为已成功 d、执行任务之前必须要获取独占式锁并检查执行结果 e、能记录事件触发与执行结果 f、有自我驱动机制执行失败的情况 g、根据设计适时释放独占式锁

简要说明一下, a、是为了业务具有辨识重复触发的能力 b、是为了防护并发执行问题 c、可以让事件执行成功之后不再重复执行 d、并发问题检查 e、既有业务使用的需要,也有技术上的辨识需要 f、业务重试机制 g、防止锁控制异常

以上要素,为了解决问题,都需要在业务代码实现时,提供技术实现。

# 2.3 事件参数设计

工作流事件发生的数据参数,需要能为业务上提供辨识“同一事件重复发生"的能力,业务描述为在某一条流程的某一个节点中发生的某个事件。具体参数类见com.seeyon.ctp.workflow.event.WorkflowEventData

其中,在8.1sp2中,有两套参数可以以前辨识基础,(除发起等特殊事件外); a private long summaryId; private long affairId; summaryId标识对应业务流程,affairId标识个人事项 这组参数依赖于应用的参数,summary 与 affair,都来自于上下文WorkflowBpmContext的参数传递,在两大应用(公文、协同)下是平台应用提供参数传递。该数据也跟业务具有更高相关度 b private String currentProcessId; private String currentActivityId; currentProcessId标识事件发生流程实例,currentActivityId标识事件发生节点 这组参数取决于流程引擎自己维护的流程实例,需要注意的是,流程引擎的流程实例与业务上的流程不完全等价,主要是撤销或者回退到发起人之后再次发起的流程,在底层引擎已经不再是同一个流程实例了。但不同实例的同一个节点的节点id标识是同一个。

以上参数其实在有条件下,都有传递,或者也可以参考以上说明考虑更复杂的设计。在8.1sp1及以下版本中,暂未加入currentProcessId参数。

# 2.4 前事件的阻塞能力设计

自从V8.0SP2LTS版本以来,流程事件控制新增了一种模式,叫【弱阻塞】,同时也区分了前事件与非前事件时的控制。 在前事件任务下,事件的执行是线程同步的;非前事件任务下,事件的执行是线程异步的。 同步线程会阻塞等待事件的实现被调用方返回一个结果。需要实现【弱阻塞】模式,则需要被调用方返回一个result对象,其类型为 com.seeyon.ctp.workflow.event.WorkflowEventResult。 其中包含 private String alertMessage = ""; private boolean weakBlock = false; alertMessage 标识开放给被调用方提示用户的消息,weakBlock标识弱阻塞标志位

两个属性,当weakBlock为 true,且alertMessage不为空时,会触发【弱阻塞】模式提示用户,但不会限制提交;而相反的当weakBlock为 false,且alertMessage不为空时,会触发【强阻塞】模式,会限制后续的提交。 业务可以根据这个能力,解决一部分两个系统不一致导致的异常问题,也可以开放给用户更自由的选择权限。

# 3、其他问题及后续规划

在当前设计下,能解决绝大部分问题,但还存在着另一类问题不能解决,该场景是ab类问题的另一类延伸: 当前事件任务执行之后,提交失败了,此时系统转交用户处理,但是用户最终选择不提交。 这类问题的本质是一个分布式事务的问题。由于为了支撑解决此类问题,需要额外设计回滚等行为,同样需要第三方系统支持事务回滚。考虑到成本过高,且从应用角度考虑,此场景并不是很多,无论是手动提交,还是能触发重复跳过、超时跳过提交等各种机制,或者是批量处理等,都代表着业务上,用户具备需要执行完整次提交的要素。

所以综合考虑暂时不支撑这个场景,后续如果凭反馈需要支撑,标准产品会在以后的版本中设计规划。

编撰人:wxju