Flowable实战:从流程混乱到优雅编排的架构演进

去年我们团队接手了一个供应链金融项目,核心的授信审批流程已经变成了"代码泥潭"——业务人员每次流程变更都需要开发团队修改代码,发布时间从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行,没人敢动
    }
}

问题诊断​:

  1. 流程硬编码​:业务规则与流程控制深度耦合
  2. 修改成本高​:每次流程调整都需要开发、测试、部署
  3. 无法监控​:审批到哪一步?谁在处理?完全黑盒
  4. 扩展困难​:新业务方接入需要重写大量代码

业务总监的原话:"我们等不起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倍提升

业务价值体现

  1. 流程可视化​:业务人员可以直接查看审批进度
  2. 灵活调整​:常规流程调整无需开发介入
  3. 合规审计​:完整的审批痕迹留存
  4. 异常处理​:自动重试、转人工等容错机制

八、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是一个优秀的工具,但就像任何工具一样,只有用在合适的地方才能发挥最大价值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值