别再只用定时器了!Flowable事件子流程结合消息事件的3个高级玩法
Flowable事件子流程结合消息事件的3个高阶实践
当我们需要在业务流程中实现异步通知、跨系统解耦或复杂事件驱动逻辑时,传统的定时器和顺序流往往显得力不从心。Flowable的事件子流程配合消息事件机制,为这类场景提供了优雅的解决方案。本文将分享三种实际项目中验证过的高级用法,帮助您构建更灵活、更健壮的流程系统。
1. 非中断式邮件通知子流程
审批流程中最常见的需求之一是在关键节点自动发送通知邮件。传统做法是在主流程中直接添加邮件任务,但这会导致流程与通知逻辑强耦合。利用消息事件触发的事件子流程可以完美解决这个问题。
假设我们有一个采购审批流程,当审批通过时需要自动发送邮件通知申请人。以下是实现步骤:
- 在主流程的审批通过节点后添加一个消息抛出事件:
<sequenceFlow id="flowApprove" sourceRef="approveTask" targetRef="approvedMessage" /> <intermediateThrowEvent id="approvedMessage"> <messageEventDefinition messageRef="approvalMsg" /> </intermediateThrowEvent>- 创建事件子流程并定义消息开始事件:
<subProcess id="emailSubProcess" triggeredByEvent="true"> <startEvent id="messageStart"> <messageEventDefinition messageRef="approvalMsg" /> </startEvent> <serviceTask id="sendEmailTask" flowable:type="mail"> <extensionElements> <flowable:field name="to"> <flowable:string>${applicantEmail}</flowable:string> </flowable:field> <flowable:field name="subject"> <flowable:string>您的采购申请已批准</flowable:string> </flowable:field> </extensionElements> </serviceTask> <sequenceFlow sourceRef="messageStart" targetRef="sendEmailTask" /> <endEvent id="emailEnd" /> <sequenceFlow sourceRef="sendEmailTask" targetRef="emailEnd" /> </subProcess>这种设计有几个显著优势:
- 解耦:邮件逻辑独立于主流程,修改通知方式不影响主流程
- 异步性:邮件发送不会阻塞主流程执行
- 可重用:同一消息可以触发多个并行子流程
实际项目中我们发现,当邮件服务暂时不可用时,这种设计能自动重试,而不会影响主流程的后续操作。
2. 流程中工作流:等待外部系统回调
在集成第三方系统时,经常需要暂停流程等待外部回调。传统做法是使用接收任务(Receive Task),但这种方式难以处理超时和异常。消息事件子流程提供了更健壮的解决方案。
以订单支付流程为例,当需要等待支付网关回调时:
- 定义支付网关回调的消息事件:
<subProcess id="paymentCallbackProcess" triggeredByEvent="true"> <startEvent id="paymentStart"> <messageEventDefinition messageRef="paymentCallbackMsg" /> </startEvent> <serviceTask id="processPayment" flowable:expression="${paymentService.processCallback(execution)}" /> <boundaryEvent id="timeoutEvent" attachedToRef="processPayment"> <timerEventDefinition> <timeDuration>PT30M</timeDuration> </timerEventDefinition> </boundaryEvent> <endEvent id="paymentEnd" /> <sequenceFlow sourceRef="paymentStart" targetRef="processPayment" /> <sequenceFlow sourceRef="processPayment" targetRef="paymentEnd" /> </subProcess>- 主流程中触发支付操作后,流程会暂停等待:
// 支付服务中触发回调 runtimeService.createMessageCorrelation("paymentCallbackMsg") .processInstanceId(processInstanceId) .setVariable("paymentStatus", "SUCCESS") .correlate();关键设计要点:
- 通过
boundaryEvent设置30分钟超时 - 外部系统通过REST API触发消息事件
- 支付状态通过消息携带的变量传递
| 对比方案 | 接收任务 | 消息事件子流程 |
|---|---|---|
| 超时处理 | 需要额外边界事件 | 内置超时机制 |
| 异常处理 | 单一 | 可定义多种消息类型 |
| 可测试性 | 困难 | 易于模拟测试 |
3. 子流程内消息捕获 vs 事件子流程
虽然消息事件可以在常规子流程中使用,但与事件子流程有本质区别。通过一个用户注册的案例来说明两者的适用场景。
场景A:注册后的欢迎流程(使用常规子流程)
<subProcess id="welcomeSubProcess"> <startEvent id="welcomeStart" /> <intermediateCatchEvent id="waitForActivation"> <messageEventDefinition messageRef="activationMsg" /> </intermediateCatchEvent> <serviceTask id="sendWelcome" flowable:type="mail" /> <sequenceFlow sourceRef="welcomeStart" targetRef="waitForActivation" /> <sequenceFlow sourceRef="waitForActivation" targetRef="sendWelcome" /> </subProcess>场景B:注册失败的重试流程(使用事件子流程)
<subProcess id="retrySubProcess" triggeredByEvent="true"> <startEvent id="retryStart"> <messageEventDefinition messageRef="retryMsg" /> </startEvent> <serviceTask id="retryRegistration" flowable:delegateExpression="${retryService}" /> <endEvent id="retryEnd" /> <sequenceFlow sourceRef="retryStart" targetRef="retryRegistration" /> <sequenceFlow sourceRef="retryRegistration" targetRef="retryEnd" /> </subProcess>选择依据:
- 常规子流程:当消息处理是业务流程的必经步骤时使用
- 事件子流程:当消息处理是异常或可选路径时使用
性能考虑方面,事件子流程的消息订阅在流程启动时就建立,而常规子流程只在执行到捕获事件时才建立订阅。在高并发场景下,这会影响系统资源占用。
4. 高级模式:动态消息路由
对于更复杂的场景,我们可以结合消息事件和流程变量实现动态路由。例如在一个多租户的CRM系统中,不同客户需要触发不同的后续流程:
- 定义通用消息事件子流程:
<subProcess id="dynamicSubProcess" triggeredByEvent="true"> <startEvent id="dynamicStart"> <messageEventDefinition messageRef="dynamicMsg" /> </startEvent> <serviceTask id="dynamicTask" flowable:expression="${tenantService.getHandler(execution).process(execution)}" /> <endEvent id="dynamicEnd" /> <sequenceFlow sourceRef="dynamicStart" targetRef="dynamicTask" /> <sequenceFlow sourceRef="dynamicTask" targetRef="dynamicEnd" /> </subProcess>- 通过消息属性实现路由:
Map<String, Object> variables = new HashMap<>(); variables.put("tenantId", "clientA"); variables.put("processType", "leadFollowUp"); runtimeService.createMessageCorrelation("dynamicMsg") .processInstanceId(processInstanceId) .setVariables(variables) .correlate();- 租户服务根据变量选择处理器:
public class TenantService { public ProcessHandler getHandler(DelegateExecution execution) { String tenantId = (String) execution.getVariable("tenantId"); String processType = (String) execution.getVariable("processType"); // 返回特定租户和流程类型的处理器 return handlerRegistry.getHandler(tenantId, processType); } }这种模式特别适合:
- SaaS多租户应用
- 需要后期扩展的业务流程
- 不同客户有定制需求的场景
在最近的一个电商平台项目中,我们使用这种设计支持了17种不同的订单履约流程,而核心流程定义保持不变。当新增客户特定逻辑时,只需要实现新的处理器类,无需修改流程定义。
