扔掉流程臃肿:低代码如何重构工作流开发

0 阅读5分钟

当传统工作流开发陷入“代码地狱”,低代码正在用一种更聪明的方式重新定义规则

从一段代码说起

先看一个典型的请假审批流程,传统开发模式下,你需要:

// 传统工作流开发示例
public class LeaveWorkflow {
    // 1. 定义流程节点
    private List<String> nodes = Arrays.asList("提交申请", "主管审批", "HR归档");
    
    // 2. 硬编码流转逻辑
    public String nextNode(String currentNode, Map<String, Object> params) {
        if ("提交申请".equals(currentNode)) {
            if (params.get("days") > 3) {
                return "部门经理审批"; // 分支逻辑
            }
            return "主管审批";
        }
        // 继续写更多if-else...
        return null;
    }
    
    // 3. 每个节点都要写handler
    @Autowired
    private ApprovalHandler approvalHandler;
    // 还有通知逻辑、超时逻辑、驳回逻辑...
}

问题很明显:流程逻辑与业务代码强耦合,改一个节点要动多处代码,上线后发现问题还得重新部署。

传统工作流开发的“臃肿”真相

过去几年,我见过太多团队在工作流开发上栽跟头:

1. 代码层面的膨胀

  • 每个流程节点至少对应一个Handler类
  • 分支判断散落在各个Service中
  • 流程图与代码实现两张皮

2. 协作层面的撕裂

  • 业务人员用Visio画图,开发人员翻译成代码
  • 需求变更时,改图、改代码、改数据库、重新上线
  • 沟通成本呈指数级增长

3. 维护层面的噩梦

  • 一个中等复杂度的OA系统,工作流相关代码占30%以上
  • 新人接手需要先看懂流程图,再看懂代码,再理解映射关系
  • 线上问题排查要翻N个文件才能串起完整链路

这些问题的本质是什么?传统开发模式把“流程定义”和“流程执行”强行分离了

图片 4.png

低代码的解法:流程即配置,配置即代码

JNPF平台的流程设计器为例,它的核心思路是:把流程的“描述”和“实现”合二为一

可视化建模,生成的是标准BPMN2.0

JNPF的流程设计器生成的不是一张图片,而是符合BPMN2.0规范的JSON Schema:

{
  "processId": "leave_approval_v2",
  "nodes": [
    {
      "id": "start_event",
      "type": "startEvent",
      "outgoing": "submit_task"
    },
    {
      "id": "submit_task",
      "type": "userTask",
      "name": "提交申请",
      "assignee": "${initiator}",
      "formKey": "leave_form",
      "outgoing": "approval_gateway"
    },
    {
      "id": "approval_gateway",
      "type": "exclusiveGateway",
      "conditionExpression": "${days > 3 ? 'manager_approve' : 'supervisor_approve'}"
    }
  ]
}

这个JSON本身就是可执行的流程定义。JNPF引擎直接解析它来驱动流程运转,不需要额外写一行Java代码。

表达式引擎替代硬编码分支

传统开发中,分支逻辑靠if-else堆砌。JNPF使用表达式引擎(基于Aviator扩展):

// 条件节点配置
{
  "condition": "days > 3 && department == '研发部'",
  "nextNode": "cto_approve"
}

表达式支持动态变量、函数调用、甚至调用Spring Bean:

// 调用外部服务判断
"@holidayService.getRemainingDays(initiator) >= days"

这意味着复杂的业务规则可以配置化,修改规则不需要重启服务。

扩展点机制,保留灵活性

低代码不是要消灭代码,而是把代码放在该放的地方。JNPF提供了两类扩展点:

节点监听器:在节点进入、完成、取消时触发

@Component
public class LeaveApprovalListener implements TaskListener {
    @Override
    public void onComplete(DelegateTask task) {
        // 自定义逻辑:发送钉钉消息、记录审计日志
        String applicant = (String) task.getVariable("initiator");
        DingTalkService.send(applicant, "您的请假单已审批通过");
    }
}

服务任务:调用任意Spring Bean

{
  "id": "auto_salary_calc",
  "type": "serviceTask",
  "delegateExpression": "${salaryService.calcLeaveDeduction}"
}

这种设计兼顾了配置的便捷性代码的灵活性

一个完整的对比:开发一个审批流程

假设要实现一个“合同审批流程”,涉及法务、财务、业务三个部门会签。

传统开发模式

  1. 设计数据库表:流程实例表、待办任务表、历史记录表...
  2. 定义流程状态枚举:DRAFTING、LEGAL_APPROVING、FINANCE_APPROVING...
  3. 编写流转Service:submit()approve()reject()...
  4. 处理并发:一个节点多人审批,谁先审?谁必须审?
  5. 处理超时:配置定时任务扫描超时任务
  6. 处理驳回:驳回后重新流转的逻辑

代码量预估:1500-2000行,开发周期:5人天

JNPF低代码模式

  1. 拖拽节点:开始→用户任务(法务)→用户任务(财务)→用户任务(业务)→结束
  2. 配置会签策略:${multiInstanceType == 'all'} 表示全部通过
  3. 设置表单权限:每个节点配置可见/可编辑字段
  4. 配置超时提醒:节点属性里设置dueDatereminder
  5. 点击发布,流程定义保存

代码量:0行(扩展点除外),开发周期:0.5人天

技术深潜:JNPF的流程引擎架构

JNPF的流程引擎采用了状态机+事件驱动的架构:

┌─────────────────────────────────────────┐
│           流程定义仓储                    │
│  (BPMN2.0 JSON / XML)                   │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│           流程引擎核心                    │
│  ┌─────────────────────────────────┐    │
│  │  解析器 → 状态机 → 执行器        │    │
│  └─────────────────────────────────┘    │
└─────────────────┬───────────────────────┘
                  │
┌─────────────────▼───────────────────────┐
│           运行时数据                     │
│  ┌──────────┐ ┌──────────┐ ┌────────┐  │
│  │实例表    │ │任务表    │ │变量表  │  │
│  └──────────┘ └──────────┘ └────────┘  │
└─────────────────────────────────────────┘

关键设计点:

1. 无状态引擎 引擎本身不存储任何状态,所有运行时数据持久化到数据库。这使得引擎可以水平扩展,多个实例同时处理不同流程。

2. 乐观锁保证一致性 流程流转涉及多表操作,JNPF使用乐观锁机制:

UPDATE wf_process_instance 
SET status = 'RUNNING', version = version + 1
WHERE id = #{id} AND version = #{oldVersion}

版本号冲突时重试,避免死锁。

3. 异步任务队列 耗时操作(如调用外部接口、发送通知)放入消息队列异步处理,不阻塞主流程:

@EventListener
public void handleTaskCreated(TaskCreatedEvent event) {
    // 异步发送通知
    asyncNotifyService.send(event.getTask());
}

低代码不是银弹,但确实是答案

有人说低代码只适合简单场景,但JNPF在企业级复杂流程中的实践表明:

  • 90%的流程逻辑可以通过可视化配置完成
  • 10%的定制需求通过扩展点解决,比传统开发更集中、更易维护
  • 流程变更周期从“周”缩短到“小时”

核心原因是:工作流本质上是一个“状态转移”问题,而状态转移天然适合用声明式配置来描述

传统开发用命令式代码描述状态转移,是在用错误的方式解决正确的问题。

结语

回到最初的问题:工作流开发为什么会臃肿?

因为我们一直在用“造轮子”的方式做重复的事情。每个项目都要重新写一遍流程引擎、重新处理并发、重新设计超时机制。

图片 12.png

低代码平台把这些基础设施抽象成了配置化的能力,让开发人员专注于业务逻辑本身。这不是偷懒,而是把精力花在更有价值的地方

如果你的团队还在用if-else堆砌工作流,或许该重新审视一下技术选型了。