流程前事件执行但流程未提交问题
# 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类问题的另一类延伸: 当前事件任务执行之后,提交失败了,此时系统转交用户处理,但是用户最终选择不提交。 这类问题的本质是一个分布式事务的问题。由于为了支撑解决此类问题,需要额外设计回滚等行为,同样需要第三方系统支持事务回滚。考虑到成本过高,且从应用角度考虑,此场景并不是很多,无论是手动提交,还是能触发重复跳过、超时跳过提交等各种机制,或者是批量处理等,都代表着业务上,用户具备需要执行完整次提交的要素。
所以综合考虑暂时不支撑这个场景,后续如果凭反馈需要支撑,标准产品会在以后的版本中设计规划。
快速跳转
