当规则比代码跑得快:我对用 LiteFlow 编排信贷业务的一点思考

一、我发现信贷业务的复杂度,本质上就是"规则"在膨胀

我一直在做的这类信贷平台,核心链路无非是进件、授信、提现、还款几步,看起来就是几个接口,但我越往深挖越发现,每个接口背后都是多资方 × 多渠道 × 多风控策略的组合爆炸:

  • 一个用户进件,要在几十家资方里按额度、风险等级、时间窗口、当日放款/拒绝配额做多维路由;
  • 同一笔提现,不同资方的绑卡方式、验证码逻辑、放款回调状态机完全不一样;
  • 同一次还款,十几家资方的还款计划拉取、试算、回调对接也是各玩各的。

我后来想明白了,这根本不是"业务逻辑复杂",而是"规则的数量和组合方式一直在长"。团队人手有限、排期又紧的时候,大家都会选眼下最省事的写法,所以同一个系统里,我看到三条链路演化出了三种完全不同的应对手段:

编排点 现在用的方式 说白了是在解决什么
授信路由 一个递归方法,八九个过滤条件挨个判断 "在一堆资方里选出一个满足所有约束的"
提现放款 状态机式的轮询,十几个状态分支 "把资方那边异步回来的结果收敛成几个确定状态"
还款 策略模式,按资方标识查表分发到不同实现 "同一个动作,每家资方走不同实现"

这三套机制单独看都没毛病,但共同点是------都在用自己的方式把"规则"硬编码死了:授信路由那几个过滤维度的顺序写死在方法里;还款策略每接一家新资方要新建类、注册、改配置表,三个地方联动;提现状态机的每个分支里又嵌套着各资方的差异化处理。这些代码本身写得都还行,问题出在------规则一变,就得发版。而信贷这个行业,规则变化的频率说实话比大多数系统都高:

  • 风控今天说要临时调某家资方的日拒量上限;
  • 运营说某个渠道要优先导到费率更低的那家;
  • 资方突然通知要维护,得在进件时间窗里临时加一段禁止时段;
  • 新接一家资方,授信、提现、还款三条链路都要各插一段差异化逻辑。

我算了下,这些变化其实都是"规则/参数/顺序"层面的调整,却被迫走一遍"改代码→测试→发布"的重流程,怎么算都不划算。这正是我想聊 LiteFlow 这类流程编排/规则引擎的原因。

二、LiteFlow 到底是个什么东西

我理解下来,LiteFlow 是一款很轻量、几乎无侵入的规则编排引擎 ,核心思路挺朴素:把"业务动作"拆成一个个独立的组件(Component) ,再用一段 EL 表达式描述这些组件该怎么编排。规则和组件是分开的------组件还是 Java 代码,规则是一段纯文本,可以扔进数据库或者配置中心,支持热更新。

2.1 组件:一个最小的、可复用的业务单元

java 复制代码
@LiteflowComponent("checkFundStatus")
public class CheckFundStatusCmp extends NodeBooleanComponent {
    @Override
    public boolean processBoolean() {
        FundConfig fundConfig = this.getContextBean(RouteContext.class).getFundConfig();
        // 判断资方是否开启,对应原来那个大方法里的第一层判断
        return fundConfig.isEnabled();
    }
}

我挺喜欢这种设计的一点是,一个组件只做一件事,职责单一,脱离整体流程也能单独写单测------这比维护一个几百行的递归方法舒服太多了。

2.2 EL 表达式:把"怎么编排"从代码里挪出来

我常用到的编排语义大概这几种:

  • THEN(a, b, c) ------ 串行,依次执行
  • WHEN(a, b, c) ------ 并行,几个互不依赖的校验同时跑,默认全过才算过(ignoreError 默认为 false),也可以配 maxWaitSeconds()percentage() 之类的参数放宽成"部分完成/超时也算过"------比如提现确认这种高并发路径上,某个资方查询偶尔超时不该拖垮整体校验
  • SWITCH(x).to(a, b, c) ------ 条件分支,按标识路由,我觉得可以直接替代那种 Map<String, Bean> 查找的写法;还可以配一个 .DEFAULT(d) 兜底分支,比如"没有资方能匹配时走人工审核"这种场景正好用得上
  • IF(cond, a, b) ------ 条件执行
  • FOR/WHILE/ITERATOR ------ 循环,比如遍历资方候选序列逐个尝试
  • CATCH(a).DO(c) ------ 异常捕获、兜底

拿授信路由那堆过滤条件举例,我会把它写成这样:

xml 复制代码
<chain name="creditRoute">
    THEN(
        checkFundStatus,
        checkTimeLimit,
        checkChannelDailyLimit,
        checkFundRejectLimit,
        checkFullLoanLimit,
        checkSpecialSource,
        checkNewOldCustomerLimit,
        checkUserLevelMatch
    );
</chain>

这段表达式不是代码,是数据------存数据库或者配置中心都行,风控或者运营在后台想调整"某个渠道要不要检查某一项""要不要换个顺序""针对某家资方要不要跳过某一层",改的就是这段文本,不用碰一行 Java 代码,改完立刻生效,不用走发布。

三、我拿三个场景推演了一下,改造之后会是什么样

3.1 授信路由:递归判断 → 责任链 + 可配置规则

现在的问题是,那个递归方法把八九个过滤条件的顺序写死了,每次跳过一个就记一条原因。逻辑挺严谨,但只要想调顺序、加减某个渠道的校验项,就得改这个方法本身。

我的想法是:把每个过滤维度拆成独立组件,"记录跳过原因"这种重复逻辑做成组件基类的一个统一钩子,不用每个组件都写一遍。规则链按渠道+资方维度可以配不同的 EL 表达式------比如某些新渠道不需要查"新老客户进件量上限",直接在规则里把这个节点删掉就行,不用在代码里加一堆 if (channel == xxx) 跳过 的特判。

而且 LiteFlow 的责任链本身就支持"哪个节点没过就终止并返回原因"这种短路语义,跟现在"跳过记原因、继续下一个"的逻辑完全不冲突,只是把硬编码的顺序变成了可配置的顺序。

3.2 还款策略模式:Map 分发 → LiteFlow SWITCH

现在这块已经是策略模式了,按资方标识做 O(1) 查找分发,"新增资方不用改路由框架代码"这点其实做得不错。我觉得 LiteFlow 在这里的增量价值,主要在策略内部的分支逻辑上:

现在每个策略实现里都有一段类似的绑卡分支:

css 复制代码
[有验证码] 校验签约 → 走还款
[无验证码] 查签约状态 → 分几种情况处理

我发现这段 if-else 在十几个资方的策略类里长得都差不多、又不完全一样------这其实是策略模式的一个软肋:骨架相同,但每个实现类都得重写一遍骨架。如果用 LiteFlow 编排,我可以把"绑卡分支判断"抽成公共组件,各资方只需要提供自己的"验签实现""签约申请实现"这些差异点,用 EL 表达式拼一下:

xml 复制代码
<chain name="repayFundA">
    IF(hasCaptcha,
        THEN(confirmSignFundA, doRepay),
        THEN(getSignStatusFundA, IF(signed, doRepay, sendCaptchaFundA))
    );
</chain>

这样一来,"接一家新资方的还款对接"就从"新建一个几十个方法的策略类"降级成"实现几个差异点组件 + 拼一段规则",开发量和测试量都能降不少。

3.3 提现状态机:LiteFlow 不是万能药

这里我得说句实话:那种跨多次异步回调、需要持久化当前状态的场景,本质上是个状态机,LiteFlow 擅长的是"一次调用内的同步编排",不能直接替代状态机框架,也替代不了状态字段驱动的轮询逻辑。

但我发现状态机里每个状态对应的处理动作 ------比如"资方拒绝了,尝试路由下一家,全部失败就冻结额度"这段逻辑,本身是可以用 LiteFlow 编排的一段子流程。所以我给自己的定位是:状态机负责"我现在在哪个状态、该往哪流转",LiteFlow 负责"到了这个状态该按什么规则做哪些事",两者是互补,不是替代。

四、我觉得能拿到的收益

  1. 规则配置化,业务方不用等发版:规则和参数存数据库或配置中心,风控、运营在后台自己调,实时生效,我们从"改代码"退成"维护组件库"。
  2. 接新资方的成本变得可预期:只要组件粒度设计得当,接一家新资方就是"实现几个差异点组件 + 写一段规则",不用再复制一个策略类改几十个方法。
  3. 可观测性能顺手接上现有监控:LiteFlow 自带责任链执行日志、节点耗时统计,直接接进已有的链路追踪和监控体系就行,不用额外造轮子。
  4. 组件天然好测:每个组件是个最小单元,脱离整体流程也能单独写单测,比测一个两百行的递归方法轻松多了。
  5. 可以慢慢来,不用推倒重来:我建议先挑一个变化最频繁、当下最痛的点试点(比如授信路由的过滤维度,或者接新资方的还款对接),验证有效果了再往其他链路铺开。

五、几个我觉得必须正视的坑

  1. "能配置"不等于"该随便配"。信贷业务对规则变更的审计要求很高------谁改的、什么时候改的、为什么改、有没有人审批。如果只是把 if-else 搬进 EL 表达式扔进数据库,却没配套的审批、灰度、回滚机制,规则变更反而从"有 code review 的一次 PR"退化成"没人审的一次数据库更新",这在信贷这种资损敏感的业务里,风险是变大了,不是变小了。
  2. 组件粒度不好把握。拆太细,规则链会膨胀到几十个节点,可读性反而更差;拆太粗,又失去了灵活编排的意义。我自己的判断标准是看"是不是一个独立的业务判断点",而不是看代码行数多少。
  3. 动态表达式是有性能代价的。EL 解析本身,加上如果引入脚本引擎做动态条件判断,都有额外开销。如果链路本身在一个高并发的核心路径上(比如提现确认),我觉得得提前压测一下,评估要不要对规则链做缓存或者预编译。
  4. 幂等、分布式锁这些底层保障,LiteFlow 管不了。现有的防重锁、状态流转的原子性,这些还是得留在组件内部或者组件的上下文里维护,编排引擎只管"执行顺序",不管"执行安全"。
  5. 团队要有个适应期。EL 语法、组件生命周期、上下文怎么传,都是新概念,得建立组件命名规范、上下文契约规范,不然组件之间靠隐式的上下文字段互相耦合,很容易又变成一坨新的"意大利面"。

六、写在最后

我想清楚了一件事:授信路由的递归判断、提现的状态机轮询、还款的策略模式,本质上是同一个问题的三种不同答案------规则老在变的时候,怎么把"变化"和"稳定的执行框架"分开。LiteFlow 这类编排引擎给我的价值,不是把复杂度消灭掉,而是把原来散落在代码各处的 if-else 和策略类,收敛成一套统一的组件库加一份可配置的规则,让规则变化的响应速度从"发版周期"缩短到"配置生效的那一刻"。

但我也提醒自己,它解决的只是"编排"问题,解决不了"审计""性能""幂等"这些问题------这些还是得靠配套的规则治理机制,和现有的分布式锁、状态机能力来兜底。对信贷这种强监管、强审计的业务,我觉得落地 LiteFlow 的心态不该是"让业务方随便改规则",而应该是"用一套统一的编排语言,把原本分散、隐性的规则,变得显式、可视化、可追溯"。

相关推荐
苏三说技术1 小时前
干掉if...else,这个规则引擎真香!
后端
xiaoshuai10241 小时前
把权限校验从手写里解放出来:RBAC 注解 + 过滤器链的设计
后端
Csvn1 小时前
Python 开发技巧 · Python 上下文管理器 —— 从 with 到 contextlib 实战
后端
Csvn1 小时前
Python 开发技巧:functools 模块深入
后端
行者全栈架构师1 小时前
PolarDB + Spring Boot 实战:从自建MySQL到云原生数据库的零停机迁移
java·后端·架构
Gopher_HBo1 小时前
moby-容器对象与状态学习
后端
xiaoshuai10241 小时前
Controller 直连了数据库、模块缠成死结:用 ArchUnit 把架构钉死
后端
陈随易13 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·后端·程序员