001 流程引擎技术侧收益
一、背景&目标
1.1 背景
-
crm技术侧在对历史代码维护和迭代开发时发现存在很多问题 1、代码逻辑混乱,不同子域业务逻辑没有做隔离,导致迭代开发时熟悉代码成本很高,难以维护,容易出现遗漏,极易出现循环依赖问题。 2、代码流程混乱,初期没有考虑到功能的扩展问题,历史代码几乎没有任何拓展接口。 3、模型充血,大泥球效应严重,一个类提供多重功能,无法精细化控制和热插拔,很多代码有问题但是不敢轻易改动,增加逻辑时易影响到原有的功能。
思考点:提到编码,我们常常说起的一个问题就是:应该采用贫血模型还是充血模型? 1.贫血模型,英文叫做 Anemic Domain Model ,是 Martin Fowler 在 2003 年提出的。贫血模型指的是领域对象中只有数据,没有行为,由于过于单薄,就好像人贫血了一样,显得不太健康。这种风格违背了面向对象的原则。
2.充血模型,至于所谓充血模型则是后人提出的,这个人还把模型分成四种:失血模型、贫血模型、充血模型和胀血模型。
综上:我们的业务对象纠集过多的业务逻辑就会变成胀血模型,也就是屎山的开始。显然现在的bpms符合胀血模型的特征。
1.2 目标
-
考虑到单体服务,业务杂糅在一起,随着业务复杂度的增加和代码风格的差异,历史代码变得越来越臃肿,必然会出现执行效率低下,线上bug激增,难以定位bug点且修复也是频繁的打补丁。
因此我们选型流程引擎时要考虑到以下要点:
1、满足需求的快速上线,仅占用极少的业务抽离和子域划分时间
2、为了解业务和规范接口将流程分为两种: a: 通用流程(通用流程提供统一能力 如入参校验,数据组装,事件后处理) b: 逻辑流程 (根据业务的不同抽离并分割逻辑,将分割好的逻辑组件化,异步的组件异步执行,串行的组件顺序执行,组件间可通过配置文件编排顺序,可监控执行时间,可回滚,可重试)
3、流程引擎占用极少的系统资源
二、选型
维度 | LiteFlow(流程编排引擎) | Flowable / Camunda / Activiti(BPMN引擎) |
📌 核心理念 | “流程即代码” | “流程即模型(BPMN 图)” |
🧠 面向人群 | 程序员 | 业务 + 程序员协作 |
🧱 建模方式 | Java 配置 / 注解 / XML/数据库规则/applo配置 | BPMN 图形化(可拖图) |
⚙️ 编排方式 | 规则链表达式 THEN, WHEN, IF | 流程图 + 流程引擎驱动 |
🏗 灵活性 | 高度灵活、轻量、可嵌套 | 更结构化、更规范 |
📊 可视化 | ❌ 不支持流程图 | ✅ 内建流程建模器(Modeler) |
🔁 支持异步/条件/并发 | ✅ 很强(内建线程池+异步组件) | ✅ 支持(但偏重同步) |
💼 典型用途 | 编排服务调用、接口逻辑、规则判断 | 审批流、业务流转、工作流 |
🔧 上手成本 | 极低(就是写代码) | 偏高(要理解 BPMN 语义) |
🧩 整合性 | 和 Spring Boot 深度整合/也可支持其他框架 | 多为标准化接口 |
⛓ 节点动态性 | ✅ 动态增加/替换组件 | ❌ 一般需要重新部署流程 |
LiteFlow 的主要 好处 总结如下:
2.1轻量、低侵入性
-
不依赖 Spring、MyBatis 等重框架,可单独使用
-
没有冗余配置,也不用引入笨重的 BPMN 模型
-
可快速集成到现有项目中,基本“开箱即用”
2.2 流程规则灵活、可配置
-
支持 字符串 DSL、EL 表达式、XML/JSON/YAML 等多种规则定义方式
-
支持流程的 热更新,可接入配置中心(如 Nacos、Apollo)
-
无需重启服务就能改流程!对业务团队极友好
2.3 可视化逻辑清晰、组件复用
-
将流程拆成独立节点(组件),每个组件只关注“做什么”
-
组件可以复用到多个流程中,类似积木搭建
-
整个流程就像组合逻辑,用编排就能完成
2.4 支持复杂流程结构
-
支持 串行、并行、条件判断(IF/ELSE)、SWITCH、多分支、循环、异常处理、异步 等结构
-
完全覆盖一般企业业务流程复杂度需求
2.5 易于扩展、调试友好
-
流程组件是标准 Java Bean,易于开发、测试、调试
-
可结合日志打印、监控系统,定位流程问题非常方便
2.6 内置能力丰富(开箱即用)
-
异常捕获、上下文管理、并发处理、标签匹配、多语言规则支持
-
社区活跃,文档齐全,易于维护
总结一句话:
业务即流程,逻辑即组件。
LiteFlow 就是把你脑子里的业务流程变成代码里的“拼图”,流程可配、组件复用,既强大又灵活。
三、解业务Demo
见代码
四、核心理念
4.1 通用流程:代码规范化
花费极少的人力去规范化代码
validate node(只要将以下代码沾到ai中,下次需要生成validate类只需要将被校验的类拷到ai就可自动生成)
public class GetAccessTokenResponseValidator {
public static void validate(GetAccessTokenResponse response) {
AssertUtils.assertNotNull(response, "[GetAccessTokenResponse] should not be null");
AssertUtils.assertNotNull(response.getBody(), "[GetAccessTokenResponse.body] should not be null");
AssertUtils.assertNotNull(
response.getBody().getAccessToken(), "[GetAccessTokenResponse.body.accessToken] should not be null");
}
}
@Data
public class GetAccessTokenResponse extends TeaModel {
@NameInMap("headers")
public Map<String, String> headers;
@NameInMap("statusCode")
public Integer statusCode;
@NameInMap("body")
public GetAccessTokenResponseBody body;
}
package com.tecdo.crm.common.utils;
import java.util.Collection;
import java.util.Set;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import com.tecdo.crm.common.exception.ValidationException;
/**
* @Description: 自定义断言校验工具类
* @Author: zuojipeng
* @Date: 2025/1/17 10:14
* @Version: 1.0
**/
public class AssertUtils {
private static final Validator VALIDATOR =
Validation.buildDefaultValidatorFactory().getValidator();
public static <T> void validate(T object) {
if (object == null) {
throw new IllegalArgumentException("The object to validate cannot be null.");
}
Set<ConstraintViolation<T>> violations = VALIDATOR.validate(object);
if (!violations.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<T> violation : violations) {
sb.append(violation.getPropertyPath())
.append(" ")
.append(violation.getMessage())
.append("; ");
}
throw new ValidationException(sb.toString());
}
}
// 断言条件是否为真
public static void assertTrue(boolean condition, String message) {
if (!condition) {
throw new IllegalArgumentException(message);
}
}
// 断言对象是否为非空
public static void assertNotNull(Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
// 断言对象是否为空
public static void assertNull(Object object, String message) {
if (object != null) {
throw new IllegalArgumentException(message);
}
}
// 断言字符串是否为空
public static void assertNotEmpty(String str, String message) {
if (str == null || str.trim().isEmpty()) {
throw new IllegalArgumentException(message);
}
}
// 断言集合是否为空
public static void assertNotEmpty(Collection<?> collection, String message) {
if (collection == null || collection.isEmpty()) {
throw new IllegalArgumentException(message);
}
}
// 断言两个值是否相等
public static void assertEquals(Object expected, Object actual, String message) {
if (expected == null && actual != null || expected != null && !expected.equals(actual)) {
throw new IllegalArgumentException(message);
}
}
// 断言条件是否为假
public static void assertFalse(boolean condition, String message) {
if (condition) {
throw new IllegalArgumentException(message);
}
}
}
adapter node (只要将以下代码沾到ai中,下次需要生成validate类只需要将被校验的类拷到ai就可自动生成)
package com.tecdo.crm.core.adapter;
import java.util.List;
import java.util.stream.Collectors;
import com.tecdo.crm.core.db.crm.entity.LeadFollowKpi;
import com.tecdo.crm.core.dto.LeadFollowKpiDTO;
/**
* @Description: 线索kpi
* @Author: zuojipeng
* @Date: 2025/1/23 10:43
* @Version: 1.0
**/
public class LeadFollowKpiAdapter {
/**
* Converts LeadFollowKpiDTO to LeadFollowKpi entity.
*
* @param dto the LeadFollowKpiDTO to convert
* @return the converted LeadFollowKpi entity
*/
public static LeadFollowKpi adapt(LeadFollowKpiDTO dto) {
if (dto == null) {
return null;
}
LeadFollowKpi entity = new LeadFollowKpi();
entity.setStatDate(dto.getStatDate());
entity.setLeadId(dto.getLeadId());
entity.setCustomerId(dto.getCustomerId());
entity.setAdvId(dto.getAdvId());
entity.setMediaPlatform(dto.getMediaPlatform());
entity.setAdvName(dto.getAdvName());
entity.setCooperationType(dto.getCooperationType());
entity.setBdId(dto.getBdId());
entity.setBdName(dto.getBdName());
entity.setBdNameEn(dto.getBdNameEn());
entity.setPaId(dto.getPaId());
entity.setPaName(dto.getPaName());
entity.setLeadCreateDate(dto.getLeadCreateDate());
entity.setLeadDiscoveryDate(dto.getLeadDiscoveryDate());
entity.setQuotationId(dto.getQuotationId());
entity.setCrmServiceItem(dto.getCrmServiceItem());
entity.setFirstAdvSpendDate(dto.getFirstAdvSpendDate());
entity.setAdvPlatSpendAmt1d(dto.getAdvPlatSpendAmt1d());
entity.setAdvPlatMarginAmt1d(dto.getAdvPlatMarginAmt1d());
entity.setCreateAccCnt1d(dto.getCreateAccCnt1d());
entity.setNewAccShopCnt1d(dto.getNewAccShopCnt1d());
return entity;
}
/**
* Converts a list of LeadFollowKpiDTOs to a list of LeadFollowKpi entities.
*
* @param dtoList the list of LeadFollowKpiDTOs
* @return the converted list of LeadFollowKpi entities
*/
public static List<LeadFollowKpi> adaptListToEntity(List<LeadFollowKpiDTO> dtoList) {
if (dtoList == null || dtoList.isEmpty()) {
return List.of();
}
return dtoList.stream().map(LeadFollowKpiAdapter::adapt).collect(Collectors.toList());
}
}
postHandler node
将通知,推送或和主流程无关的node放在异步的后置节点中执行,用规则语句when控制
4.2 业务流程 (核心流程):规则组件化
解业务,详细流程见代码
五、相关文档和资料
1.https://liteflow.cc/pages/5816c5/#%E5%89%8D%E8%A8%80 2.《领域驱动设计 —— 软件核心复杂性应对之道》----Eric Evans