去年我们团队接手了一个供应链金融项目,核心的授信审批流程已经变成了"代码泥潭"——业务人员每次流程变更都需要开发团队修改代码,发布时间从2周延长到2个月。
一、噩梦开始:一个审批流程的崩溃
当时的授信审批代码长这样:
// 最初的硬编码审批流程 - 典型的"大泥球"架构
@Service
public class CreditApprovalService {
public void processApplication(CreditApplication app) {
// 阶段1: 初步审核
if (app.getAmount() > 1000000) {
// 大额需要风控初审
RiskReview riskReview = riskService.firstReview(app);
if (riskReview.getScore() < 60) {
app.setStatus(Status.REJECTED);
app.setRejectReason("风控评分不足");
return;
}
}
// 阶段2: 根据金额路由
if (app.getAmount() <= 500000) {
// 50万以下业务经理审批
boolean approved = businessManagerService.approve(app);
if (!approved) {
app.setStatus(Status.NEED_DEPARTMENT_APPROVAL);
// 进入部门审批流程...
}
} else if (app.getAmount() <= 2000000) {
// 200万以下需要部门会签
List<Approval> approvals = departmentService.collectSignatures(app);
long approveCount = approvals.stream().filter(Approval::isApproved).count();
if (approveCount < 2) {
app.setStatus(Status.NEED_VP_APPROVAL);
// 进入副总裁审批...
}
} else {
// 200万以上需要风险委员会
boolean committeeApproved = riskCommitteeService.vote(app);
if (!committeeApproved) {
app.setStatus(Status.REJECTED);
return;
}
}
// 还有10多种特殊情况的处理逻辑...
// 方法已经超过300行,没人敢动
}
}
问题诊断:
- 流程硬编码:业务规则与流程控制深度耦合
- 修改成本高:每次流程调整都需要开发、测试、部署
- 无法监控:审批到哪一步?谁在处理?完全黑盒
- 扩展困难:新业务方接入需要重写大量代码
业务总监的原话:"我们等不起2个月,市场变化太快了!"
二、技术选型:为什么是Flowable?
备选方案对比
我们评估了三个主流方案:
方案A:自研状态机
// 自研状态机示例
public class SimpleStateMachine {
private Map<State, Map<Event, Transition>> transitions = new HashMap<>();
public void addTransition(State from, Event event, State to) {
transitions.computeIfAbsent(from, k -> new HashMap<>()).put(event, to);
}
// 问题:功能有限,需要自己实现持久化、监控、回退等
}
方案B:Activiti
- 成熟度高,但架构较重
- 代码库历史包袱多
- 社区活跃度下降
方案C:Flowable
- 轻量级,API设计现代
- 基于BPMN 2.0标准
- 活跃的社区和良好的文档
决策依据:
// Flowable的简洁API让我们最终选择了它
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 启动流程只需要一行代码
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"creditApproval",
businessKey,
variables
);
三、架构重构:从代码到配置的转变
第一阶段:流程建模
我们用BPMN重新设计了审批流程:
<!-- credit-approval.bpmn -->
<process id="creditApproval" name="授信审批流程">
<!-- 开始事件 -->
<startEvent id="start" />
<!-- 根据金额路由 -->
<exclusiveGateway id="amountGateway">
<sequenceFlow id="flow1" sourceRef="amountGateway" targetRef="businessApproval">
<conditionExpression>x${amount <= 500000}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow2" sourceRef="amountGateway" targetRef="departmentApproval">
<conditionExpression>x${amount > 500000 && amount <= 2000000}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="amountGateway" targetRef="riskCommitteeApproval">
<conditionExpression>x${amount > 2000000}</conditionExpression>
</sequenceFlow>
</exclusiveGateway>
<!-- 并行会签:需要3个部门经理中至少2人同意 -->
<userTask id="departmentApproval" name="部门会签"
flowable:candidateGroups="dept_managers">
<multiInstanceLoopCharacteristics
flowable:collection="deptManagerList"
flowable:elementVariable="deptManager">
<completionCondition>x${nrOfCompletedInstances >= 2}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
</process>
第二阶段:服务集成
// 重构后的审批服务 - 职责单一,只关注业务逻辑
@Service
public class CreditApprovalService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
public String startApprovalProcess(CreditApplication app) {
Map<String, Object> variables = new HashMap<>();
variables.put("application", app);
variables.put("amount", app.getAmount());
variables.put("applicant", app.getApplicantId());
// 启动流程实例
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"creditApproval",
app.getId().toString(),
variables
);
return instance.getId();
}
// 业务经理审批任务
public void businessManagerApprove(String taskId, boolean approved, String comment) {
Map<String, Object> taskVariables = new HashMap<>();
taskVariables.put("businessApproved", approved);
taskVariables.put("businessComment", comment);
taskService.complete(taskId, taskVariables);
}
}
第三阶段:事件驱动架构
// 流程事件监听器 - 实现业务与流程解耦
@Component
public class CreditProcessEventListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
String eventName = execution.getEventName();
String processInstanceId = execution.getProcessInstanceId();
if (ExecutionListener.EVENTNAME_START.equals(eventName)) {
// 流程启动事件
CreditApplication app = (CreditApplication)
execution.getVariable("application");
sendNotification(app.getApplicantId(), "您的申请已提交,正在审批中");
} else if (ExecutionListener.EVENTNAME_END.equals(eventName)) {
// 流程结束事件
Boolean approved = (Boolean) execution.getVariable("finalApproved");
if (Boolean.TRUE.equals(approved)) {
createCreditAccount(execution);
}
}
}
private void createCreditAccount(DelegateExecution execution) {
// 异步创建授信账户
CompletableFuture.runAsync(() -> {
CreditApplication app = (CreditApplication)
execution.getVariable("application");
creditAccountService.createAccount(app);
});
}
}
四、核心技术决策的深度思考
决策1:流程变量 vs 业务数据
问题:哪些数据应该放在流程变量中?哪些应该存业务表?
// 错误做法:把整个业务对象塞进流程变量
public void startProcess(CreditApplication app) {
// 反例:大对象影响流程性能
variables.put("application", app); // 包含几十个字段的大对象
runtimeService.startProcessInstanceByKey("process", variables);
}
// 正确做法:只放流程需要的数据
public void startProcess(CreditApplication app) {
Map<String, Object> variables = new HashMap<>();
variables.put("applicantId", app.getApplicantId());
variables.put("amount", app.getAmount());
variables.put("productType", app.getProductType());
// 只放路由和决策需要的数据
runtimeService.startProcessInstanceByKey("process", variables);
}
决策依据:
- 流程变量:只存储流程控制需要的数据(< 1KB为佳)
- 业务数据:通过businessKey关联到业务表
决策2:同步 vs 异步服务任务
// 方案A:同步服务任务(简单但阻塞)
@Slf4j
@Service
public class SyncCreditScoreService implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
String applicantId = (String) execution.getVariable("applicantId");
// 同步调用,可能耗时较长
CreditScore score = creditService.calculateScore(applicantId);
execution.setVariable("creditScore", score.getValue());
log.info("信用评分计算完成: {}", score.getValue());
}
}
// 方案B:异步服务任务(复杂但非阻塞)
@Service
public class AsyncCreditScoreService {
@Autowired
private RuntimeService runtimeService;
public void startAsyncScoring(String processInstanceId, String applicantId) {
// 发送消息,立即返回
messageService.sendScoringRequest(processInstanceId, applicantId);
}
@EventListener
public void handleScoringResult(ScoringResultEvent event) {
// 收到结果后继续流程
runtimeService.signal(event.getProcessInstanceId());
}
}
选择标准:
- 同步任务:执行时间<3秒,逻辑简单
- 异步任务:执行时间长,或需要外部系统集成
决策3:网关策略选择
// 排他网关 vs 并行网关的决策框架
public class GatewayStrategy {
// 场景1:互斥条件选择 - 用排他网关
public void exclusiveGatewayExample() {
// 金额路由:50万以下→A,50-200万→B,200万以上→C
// 同一时间只会走一条路径
}
// 场景2:并行审批 - 用并行网关
public void parallelGatewayExample() {
// 风险审批、合规审批、业务审批可以同时进行
// 所有路径都要执行
}
// 场景3:复杂条件 - 用事件网关
public void eventGatewayExample() {
// 等待外部事件:客户补充材料、市场变化通知等
// 基于事件而不是条件判断
}
}
五、性能优化实战
数据库优化
-- 关键索引设计
CREATE INDEX idx_act_ru_execution_procdef ON act_ru_execution(PROC_DEF_ID_);
CREATE INDEX idx_act_ru_task_procinst ON act_ru_task(PROC_INST_ID_);
CREATE INDEX idx_act_ru_variable_execution ON act_ru_variable(EXECUTION_ID_);
-- 历史数据归档策略
CREATE EVENT archive_flowable_data
ON SCHEDULE EVERY 1 DAY
DO BEGIN
-- 将3个月前的完成流程移到历史表
INSERT INTO act_hi_procinst_archive
SELECT * FROM act_hi_procinst
WHERE END_TIME_ < DATE_SUB(NOW(), INTERVAL 90 DAY);
DELETE FROM act_hi_procinst
WHERE END_TIME_ < DATE_SUB(NOW(), INTERVAL 90 DAY);
END;
缓存策略
@Configuration
public class FlowableCacheConfig {
@Bean
public ProcessEngineConfigurationImpl processEngineConfiguration() {
SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
// 开启流程定义缓存
config.setProcessDefinitionCache(new DefaultProcessDefinitionCache());
config.setProcessDefinitionCacheLimit(1000); // 缓存1000个流程定义
// 开启用户信息缓存
config.setUserEntityCache(new DefaultUserEntityCache(500));
config.setGroupEntityCache(new DefaultGroupEntityCache(500));
return config;
}
}
六、踩坑记录与解决方案
坑1:事务边界问题
// 错误示例:事务中包含远程调用
@Service
public class ProblematicApprovalService {
@Transactional
public void approveApplication(String taskId) {
// 1. 完成任务(数据库操作)
taskService.complete(taskId);
// 2. 调用外部系统(可能超时或失败)
externalSystem.notifyApproval(taskId); // 远程调用,风险点!
// 如果这里失败,整个事务回滚,但任务已经完成了
}
}
// 解决方案:拆分事务边界
@Service
public class FixedApprovalService {
@Transactional
public void completeTask(String taskId) {
// 只完成数据库操作
taskService.complete(taskId);
}
// 新事务中处理外部调用
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handlePostApproval(String taskId) {
try {
externalSystem.notifyApproval(taskId);
} catch (Exception e) {
// 外部调用失败不影响主流程
log.error("通知外部系统失败", e);
}
}
}
坑2:变量序列化问题
// 错误:存储不可序列化的对象
public void problemMethod() {
Map<String, Object> variables = new HashMap<>();
// Hibernate代理对象不可序列化
variables.put("user", hibernateUser); // 可能包含懒加载代理
runtimeService.startProcessInstanceByKey("process", variables);
}
// 解决方案:使用DTO或基本类型
public void solutionMethod() {
Map<String, Object> variables = new HashMap<>();
// 只传递需要的数据
variables.put("userId", user.getId());
variables.put("userName", user.getName());
variables.put("department", user.getDepartment().getName());
runtimeService.startProcessInstanceByKey("process", variables);
}
七、实施效果与数据对比
性能指标对比
| 指标 | 重构前 | 重构后 | 提升幅度 |
|---|---|---|---|
| 流程变更发布时间 | 2个月 | 2天 | 97% |
| 审批平均耗时 | 3.5天 | 1.2天 | 66% |
| 系统可用性 | 99.5% | 99.95% | 显著提升 |
| 开发效率 | 低 | 高 | 3倍提升 |
业务价值体现
- 流程可视化:业务人员可以直接查看审批进度
- 灵活调整:常规流程调整无需开发介入
- 合规审计:完整的审批痕迹留存
- 异常处理:自动重试、转人工等容错机制
八、Flowable适用场景评估
适合使用Flowable的场景
// 场景1:复杂审批流程
public boolean isSuitableForFlowable(Requirement req) {
return req.hasComplexRouting() || // 多条件路由
req.hasMultipleParticipants() || // 多参与者
req.needsAuditTrail() || // 需要审计跟踪
req.changesFrequently(); // 流程经常变更
}
// 场景2:长时间运行流程
public class LongRunningProcess {
// 采购流程:申请→比价→合同→付款→验收,可能持续数月
// Flowable可以持久化状态,支持中断恢复
}
// 场景3:需要SLA监控
public class ProcessMonitoring {
// 监控每个节点的处理时长
// 自动预警超时任务
}
不适合使用Flowable的场景
// 场景1:简单CRUD操作
public class SimpleCRUD {
// 用户注册、数据查询等简单操作
// 引入工作流是过度设计
}
// 场景2:高性能实时处理
public class RealTimeProcessing {
// 股票交易、游戏帧处理等
// 工作流引擎的开销不可接受
}
// 场景3:算法密集型任务
public class AlgorithmProcessing {
// 图像识别、数据分析等
// 流程引擎不擅长复杂计算
}
九、架构师的经验总结
1. 流程设计原则
// 原则1:一个流程只做一件事
public class SingleResponsibilityPrinciple {
// 不要把订单处理、库存更新、物流跟踪放在一个流程里
// 拆分为:订单审批流程、库存分配流程、物流调度流程
}
// 原则2:流程与业务逻辑分离
public class SeparationOfConcerns {
// 流程负责:谁在什么时候做什么
// 业务服务负责:具体怎么做
}
// 原则3:考虑异常处理
public class ExceptionHandling {
// 每个节点都要有超时、重试、转人工的备选方案
}
2. 团队协作模式
成功要素:
- 业务分析师:负责BPMN流程设计
- 开发工程师:实现服务任务和事件监听
- 运维工程师:负责性能监控和故障处理
工具链:
- Flowable Modeler:业务流程设计器
- Flowable Task:任务管理界面
- 自定义监控看板:实时流程监控
十、写在最后
这次Flowable实施给团队带来的最大价值不是技术层面的,而是思维模式的转变。我们从"代码实现流程"变成了"流程驱动开发",业务人员真正参与到了系统设计中。
最重要的经验:技术选型不是选择最强大的工具,而是选择最适合团队和业务现状的工具。Flowable在我们的场景中成功,是因为它恰好解决了我们的核心痛点——流程可变性和可维护性。
对于正在考虑工作流引擎的团队,我的建议是:先明确要解决什么问题,再选择工具。Flowable是一个优秀的工具,但就像任何工具一样,只有用在合适的地方才能发挥最大价值。
2500

被折叠的 条评论
为什么被折叠?



