规则引擎与商业CRM的完美邂逅:将智能决策融入商业扩展

一、背景介绍

商业CRM系统的商机模块业务复杂、场景繁多、规则调整频繁,商机流转效率一定程度决定了销售开单的效率。如何高效配合产品侧完成业务规则调整,商机流转经历了硬编码到半配置化的优化升级,过程中遇到了一些问题,也总结了一些经验,今天来和大家掰开揉碎了讲一讲这其中遇到的问题和解决方案。

1.1 什么是CRM

先看一下CRM的官方定义: CRM (Customer Relationship Management):客户关系管理,是指企业为提高核心竞争力,利用相应的信息技术以及互联网技术协调企业与顾客间在销售、营销和服务上的交互,从而提升其管理效率,向客户提供创新式的个性化的客户交互和服务的过程。

其最终目标是:吸引新客户、保留老客户以及将已有客户转为忠实客户,增加市场

可以这么简单的理解,CRM的核心价值就是:将潜在客户更高效的转化为客户,将客户更高效的转化为长期客户

介绍完什么是CRM,那CRM系统就很容易理解了,用于支撑企业进行客户关系管理的系统,就可以称作CRM系统。这个定义比较宽泛,每个公司对CRM中功能边界也不完全一样,大家初步理解这个概念就行。比如转转商业CRM系统,主要包含:商机管理、客户管理、销售/运营人员管理、业绩管理、效率监控等功能模块。

1.2 商机业务介绍

要想把潜在客户变成客户,就需要销售人员进行跟进,对潜在客户进行产品的售卖,用户购买产品后,变成客户。这个潜在客户,我们称作"商机",也就是一次成单的机会,有的CRM系统中,也称为"线索"。 上图是潜在用户到客户简单的流转图

从商机生成到最终成单,销售跟进过程中,涉及一些概念,比如商机池(公海池、私海池)、商机状态/来源/类型/等级、商机的流转等。

商机池 :各种商机的集合。
公海池 :该集合里的商机当前不归属于任何销售,所有销售均可以看到公海里的商机。
私海池 :绑定了特定销售的商机集合,比如销售A的商机私海池,只有销售A可以看到和跟进,其他销售不可见、不可跟进。
商机流转 :商机在跟进过程中,被不同的销售跟进,状态发生不同的变化。
流转规则:流转过程中会有各种各样的业务规则限制,比如销售最多可以认领100条商机、负责手机类目的销售不能认领电脑类目的商机、销售刚放弃的商机不能立马重新认领、单位时间内不进行电话沟通的商机将流出销售私海等等。

二、商机流转遇到的问题

2.1 商机流转业务特点

这里举一个例子来说明商机的流转,业务背景:商机对应用户主营类目为手机,销售A、B负责手机类目,销售C负责电脑类目,销售D也负责手机类目。 这是一个简单的流程,实际流程比这个复杂。从这个简易的流程介绍中,可以窥见部分商机流转模块的业务特点,总结起来有三点:

  • 状态的多样性

  • 状态间转换场景繁多

  • 流转规则复杂多变

2.2 商机流转业务痛点

之前商业CRM中关于流转的处理逻辑,好多都是硬编码,举个销售认领某条商机的例子:

java 复制代码
//状态校验
if(checkClueStatus(param)){
	return "状态不合法";
}
//绑定人校验
if(checkClueBindUser(param)){
	return "上一个绑定人不可以为···";
}
//私海容量校验
if(checkPrivateClueCount(param)){
	return "私海库已满,无法操作··";
}
//类目校验
if(checkClueCate(param)){
	return "类目不匹配,无法操作··";
}
//任务是否完成校验
if(checkClueTaskFinished(param)){
	return "任务未完成,无法操作··";
}  ······
bind(param);//绑定操作
log();//日志记录操作

从代码中可以看出,销售从公海进行认领一条自己觉得有价值的商机时,并不是直接就让该商机流入到该销售私海中,这个过程会有各种规则的业务校验。

2.2.1 痛点

  1. 硬编码实现的业务,维护成本大;
  2. 业务规则经常调整,难以应对变化,产研配合效率低;
  3. 业务规则调整需要配合修改代码,重新上线后生效。

2.2.2 诉求

  1. 规则抽取成可配置项,调整方便;
  2. 校验相关阈值,可随时动态调整,无需上线;
  3. 代码优雅度适当提高。

带着我们的痛点和诉求,接下来看看可以找到什么样的解决方案。然后我们进入了调研阶段,发现对于这种策略比较密集的业务,规则引擎是一个可参考方向,然后就开始了解规则引擎相关细节,了解完后,发现和业务问题匹配度很高。接下来,先介绍一下规则引擎相关的基本知识。

三、什么是规则引擎

3.1 什么是规则

规则就是:条件 --> 推理 --> 结果。一个完整的规则是需要这三部分元素的,平时大家讨论规则,某些时候只是在讨论第一部分的条件,比如学校的行为规范,咱们国家的各种法律条文,可能关注的部分是需要满足什么条件(不能做什么什么),后面的推理和结果省去了。比如学生准则里的不辱骂同学,这是一个条件,那怎么算辱骂呢,这是推理过程,辱骂后会发生什么呢,会受到老师批评,这是结果。

那什么是推理引擎和规则引擎呢?

  • 推理引擎:如果由程序来处理推理过程,那么这个程序就叫做推理机/推理引擎。
  • 规则引擎:基于规则的推理机易于理解、易于获取、易于管理,被广泛采用。这种推理引擎被称为"规则引擎"。是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。

3.2 推理引擎原理

  • 模式匹配器决定选择执行哪个规则,何时执行规则;
  • 议程管理模式匹配器挑选出来的规则的执行次序;
  • 执行引擎负责执行规则和其他动作。

推理引擎通过决定哪些规则满足事实或目标,并授予规则优先级,满足事实或目标的规则被加入议程,具体过程:

  1. 将初始数据(fact)输入至工作内存(Working Memory)。
  2. 使用Pattern Matcher将规则库(Rules repository)的规则(rule)和数据(fact)比较。
  3. 如果执行规则存在冲突(conflict),即同时激活了多个规则,将冲突的规则放入冲突集合。
  4. 解决冲突,将激活的规则按顺序放入Agenda
  5. 执行Agenda中的规则。
  6. 重复步骤2至5,直到执行完毕Agenda中的所有规则。

推理方式分为正向推理和反向推理 ::: block-1

  • 正向推理(演绎法):事实驱动,正向推理,直到无事实可用或者得出结论为止;
  • 反向推理(归纳法) :目标驱动,提出假设,寻找支持该假设的证据,如果能找到证明,结论正确,如果不能,换假设继续。 ::: 常见的模式匹配算法有Rete,LFA,TREAI,LEAPSRete 算法是目前效率最高的一个演绎法推理算法,许多规则引擎都是基于Rete算法来进行推理计算的。

3.3 Rete算法

3.3.1 Rete算法简介

  1. Rete在拉丁语中是"net",有网络的意思。Rete算法由Carnegie Mellon University的Dr Charles L. Forgy设计发明,是一个用来实现产生式规则系统(production/inference)的高效模式匹配算法。
  2. RETE算法可以分为两部分:规则编译(rule compilation)和运行时执行(runtime execution)。
    • 规则编译:是指根据规则集生成推理网络的过程。
    • 运行时执行:指将数据送入推理网络进行筛选的过程。
  3. 相关概念:
    • 事实(fact:对象之间及对象属性之间的关系
    • 规则(rule :是由条件和结论构成的推理语句,一般表示为if...Then。一个规则的if部分称为LHSleft-hand-side),then部分称为RHSright hand side)。
    • 模式(module:就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。

3.3.2 Rete算法原理

这是Rete算法的原理图,Rete算法涉及两种网络和6种不同作用的节点。

  • 网络节点Type NodeSelect NodeJoin NodeTeminal NodeAlpha MemoryBeta Memory
  • alpha网络 :过滤working memory,找出符合规则中每一个模式的集合,生成alpha memory(满足该模式的集合)。
  • Beta网络 :有两种类型的节点Beta MemoryJoin Node。前者主要存储Join完成后的集合。后者包含两个输入口,分别输入需要匹配的两个集合,由Join节点做合并工作传输给下一个节点。

3.3.2.1 Rete推理网络生成过程

  1. 创建root节点(根节点),推理网络的入口。
  2. 拿到规则1,从规则1中取出模式1(模式就是最小的原子条件,所以规则模式的关系是1:n)。
    • a) 检查模式1中的参数类型,如果是新类型,添加一个类型节点。
    • b) 检查模式1对应的Alpha节点是否存在,如果存在记录下节点的位置;如果没有,将模式1作为一个Alpha节点加入到网络中。同时根据Alpha节点建立Alpah内存表。
    • c) 重复b,直到处理完所有模式。
    • d) 组合Beta节点:Beta(2)左输入节点为Alpha(1),右输入节点为Alpha(2);Beta(i)左输入节点是Beta(i-1),右输入节点为Alpha(i),并将两个父节点的内存表内联成为自己的内存表。
    • e) 重复d,直到所有Beta节点处理完毕。
    • f) 将动作Then部分封装成最后节点做为Beta(n)。
  3. 重复2,直到所有规则处理完毕。

3.3.2.2 Rete匹配过程

  1. 导入需要处理的事实到facts集合中。
  2. 如果facts不为空,选择一个fact进行处理。否则停止匹配过程。
  3. 选择alpha网的第一个节点运行(建立网络的时候设定的),通过该节点则进入alpha网的下一个节点,直到进入alpha memory。否则跳转到下一条判断路径。
  4. 将alpha memory的结果加入到beta memory中,如果不为Terminal节点,则检测另一个输入集合中是否存在满足条件的事实,满足则执行join,进入到下一个beta memory重复执行3。若另一个输入集合无满足条件的事实,返回到2。如果该节点为Terminal节点,执行ACT并添加到facts中。

3.3.3 Rete算法实例

上面介绍的这些都偏理论化,可能有一点不太容易理解,没关系,接下来咱们用一个具体的例子,看看Rete算法到底是如何运行的。咱们依然以这个选拔篮球苗子的例子来说明。

  1. 首先看下对应的网络图
  2. Rete匹配过程
  3. 匹配过程详解
    • A节点 :拿StudentFact的年级数值进行年级匹配,如果年级符合条件,则把该StudentFact的引用记录到A节点的alpha内存区中,退出年级匹配。
    • B节点 :拿StudentFact的性别内容进行性别匹配,如果性别符合条件,则把该StudentFact的引用记录到B节点的alpha内存区中,然后找到B节点左引用的Beta节点,也就是C节点。
    • C节点 :C节点找到自己的左引用也就是A节点,看看A节点的alpha内存区中是否存放了StudentFact的引用,如果存放,说明年级和性别两个条件都符合,则在C节点的Beta内存区中存放StudentFact的引用,退出性别匹配。
    • D节点 :拿StudentFact的年龄数值进行年龄条件匹配,如果年龄符合条件,则把该StudentFact的引用记录到D节点的alpha的内存区中,然后找到D节点的左引用的Beta节点,也就是E节点。
    • E节点 :E节点找到自己的左引用也就是C节点,看看C节点的Beta内存区中是否存放了StudentFact的引用,如果存放,说明年级,性别,年龄三个条件符合,则在E节点的Beta内存区中存放StudentFact的引用,退出年龄匹配。
    • F节点 :拿StudentFact的身体数值进行身体条件匹配,如果身体条件符合,则把该StudentFact的引用记录到D节点的alpha的内存区中,然后找到F节点的左引用的Beta节点,也就是G节点。
    • G节点 :G节点找到自己的左引用也就是E节点,看看E节点的Beta内存区中是否存放了StudentFact的引用,如果存放,说明年级,性别,年龄,身体四个条件符合,则在G节点的Beta内存区中存放StudentFact的引用,退出身体匹配。
    • H节点 :拿StudentFact的身高数值进行身高条件匹配,如果身高条件符合,则把该StudentFact的引用记录到H节点的alpha的内存区中,然后找到H节点的左引用的Beta节点,也就是I节点。
    • I节点 :I节点找到自己的左引用也就是G节点,看看G节点的Beta内存区中是否存放了StudentFact的引用,如果存放了,说明年级,性别,年龄,身体,身高五个条件都符合,则在I节点的Beta内存区中存放StudentFact引用。同时说明该StudentFact对象匹配了该规则,形成一个议程,加入到冲突区,执行该条件的结果部分:该学生是一个篮球苗子。

3.3.4 Rete算法的优点

  • 可共享nodememory,提高效率;
  • memory存储中间结果,空间换时间,避免重复计算;
  • Rete 匹配速度与规则数目无直接关系,因为fact只有满足本节点才会继续向下沿网络传递。

3.3.5 Rete算法的缺点

规则的条件与facts的数目如果过大,对应memory会指数级升高,极端情况下会耗尽系统资源。

3.3.6 Rete算法使用建议

  • 容易变化的规则尽量置后匹配,可以减少规则的变化带来规则库的变化。
  • 约束性较为通用或较强的模式尽量置前匹配,可以避免不必要的匹配。
  • 针对 Rete 算法内存开销大和facts增加删除影响效率的问题,技术上应该在 AlphaMemoryBetaMemory 中,只存储指向内存的指针,并对指针建里索引(可用 hash 表或者非平衡二叉树)。
  • Rete 算法 JoinNode 可以扩展为 AndJoinNodeOrJoinNode,两种节点可以再进行组合。

看完这个匹配的过程,相信大家对规则引擎中常用的Rete算法有了一定的了解。回到规则引擎,那为啥用规则引擎呢,也就是它有何优势,以及有哪些适用的场景呢?

3.4 规则引擎优势

  1. 高灵活性:规则保存在知识库中,规则变动可以轻易做出修改。
  2. 容易掌控:规则比过程代码更易于理解,因此可以有效地来弥补业务人员和开发人员之间的沟通问题。
  3. 降低复杂度:在程序中编写大量的判断条件,很可能是会造成一场噩梦。使用规则引擎却能够通过一致的表示形式,更好的处理日益复杂的业务逻辑。开发人员也可以更关注逻辑处理,而无需过多关注逻辑判断。
  4. 可重用性:规则集中管理,可提高业务的规则的可重用性。而且,传统的代码程序通常会添加不必要的变数,很难进行重复利用。
  5. 规则外部化:即有利于规则知识的复用,也可避免改变规则时带来的代码变更问题。

3.5 规则引擎使用场景

一般多用于规则较多且规则调整频繁的业务场景,比如:积分规则、计费系统、信用风险评估、监控告警、工作流系统。

3.6 规则引擎的分类

网上有两种分类方式,这里我列举出来,供大家了解。

  • 基于实现方式
  • 基于完成度和功能配置

四、商机流转问题解决方案

了解了上面这些业务背景以及遇到的问题,也熟悉了规则引擎的理论知识,现在需要制定具体的解决方案了,我们怎么做的呢?市面有各种各样的规则引擎,先进行技术选型,这里列举下当前主流规则引擎优缺点。 通过各方面综合评估,重点放到了DroolseasyRule两者,且easyRule最终胜出。

4.1 Drools和easyRule对比

确定了要使用easyRule就得知道easyRule如何使用的,先介绍下其相关概念和使用方法。

4.2 easyRule插件介绍

4.2.1 规则说明

  • name:规则命名空间中的唯一规则名称
  • description:规则的简要描述
  • priority:规则的优先级
  • facts:触发规则时的一组已知事实
  • conditions:在给定一些事实的情况下,为了应用该规则,需要满足的一组条件
  • actions:满足条件时要执行的一组操作(可能会添加/删除/修改事实)

4.2.2 规则定义方式

  • 通过在POJO上添加注解来声明
  • 通过RuleBuilder API编程

4.2.3 组合规则用法

Easy Rules提供了3种CompositeRule的实现。

  • UnitRuleGroup:要么应用所有规则,要么不应用任何规则。--要么全用要么不用
  • ActivationRuleGroup:激活规则组触发第一个适用规则并忽略组中的其他规则。--首个选用
  • ConditionalRuleGroup:条件规则组将具有最高优先级的规则作为条件,如果具有最高优先级的规则的计算结果为true,那么将触发其余的规则。--优先级最高说了算

4.2.4 自定义优先级

值越低优先级越高。要覆盖此行为,可重写compareTo()方法以提供自定义优先级策略。

4.2.5 引擎执行模式

  • skipOnFirstAppliedRule:当一个规则成功应用时,跳过余下的规则。--一个成功,不管其他
  • skipOnFirstFailedRule:当一个规则失败时,跳过余下的规则。--一个失败,不管其他
  • skipOnFirstNonTriggeredRule:当一个规则未触发时,跳过余下的规则。--一个不符合,不管其他
  • rulePriorityThreshold:当优先级超过指定的阈值时,跳过余下的规则。--优先级达标,不管其他

4.2.6 多种监听器可供选择

支持自定义规则监听器、规则引擎监听器。

4.2.7 表达式语言支持

支持MVELSPEL表达式语言,可通过编程方式定义规则。

4.2.8 规则中的错误处理

  • 对于条件求值过程中可能发生的任何运行时异常(丢失事实、表达式中输入错误等),引擎将记录一个警告,并认为条件求值为false。
  • 对于任何在执行操作时可能发生的运行时异常(丢失事实、表达式中输入错误等),该操作将不会执行,引擎将记录一个错误。

4.3 easyRule使用样例

还是用筛选篮球苗子的例子

  1. 定义一个学生实体类
java 复制代码
public class Student {
    /**
     * 年级
     */
    private Integer grade;
    /**
     * 性别
     */
    private String gender;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 是否强壮
     */
    private Boolean isStrong;
    /**
     * 身高
     */
    private Integer height;
    /**
     * 是否一个好苗子
     */
    private Boolean isGoodSeed = true;

}
  1. 定义规则(有多种方式,我列举几种)
java 复制代码
 //创建规则1-年级
Rule rule1 = new MVELRule()
        .name("grade rule")
        .description("判断一个学生是否是一个篮球好苗子-年级")
        .priority(1)
        .when("student.getGrade() <= 3")
        .then("System.out.println(\"年级-不是好苗子\");")
        .then("student.setIsGoodSeed(false);");
java 复制代码
//创建规则2-性别
Rule rule2 = new MVELRuleFactory(new YamlRuleDefinitionReader()).
        createRule(new FileReader(
                ResourceUtils.getFile("classpath:gender-rule.yml")));

规则2需要的yml文件内容如下:

yml 复制代码
name: "gender rule"
description: "判断一个学生是否是一个篮球好苗子-性别"
priority: 2
condition: "student.getGender().equals(\"girl\")"
actions:
  - "System.out.println(\"性别-不是好苗子\");student.setIsGoodSeed(false);"
java 复制代码
//创建规则3-年龄
  Rule rule3 = new MVELRuleFactory(new JsonRuleDefinitionReader()).
          createRule(new FileReader(
                  ResourceUtils.getFile("classpath:age-rule.json")));
java 复制代码
//创建规则4-是否强壮
Condition condition = new MVELCondition("!student.getIsStrong()");
Action action = new Action() {
    @Override
    public void execute(Facts facts) throws Exception {
        Student student1 = (Student) facts.get("student");
        student1.setIsGoodSeed(false);
        System.out.println("强壮-不是好苗子");
    }
};
Rule rule4 = new RuleBuilder()
        .name("strong rule")
        .description("判断一个学生是否是一个篮球好苗子-是否强壮")
        .priority(4)
        .when(condition)
        .then(action).build();
java 复制代码
@Rule(name = "height rule" ,description = "判断一个学生是否是一个篮球好苗子-身高")
public class HeightRule {
    @Condition
    public boolean checkHeight(){ return student.getHeight() <= 170;}

    @Action
    public void action(){
        System.out.println("身高-不是好苗子");
        student.setIsGoodSeed(false);
    }
    private Student student;
    public HeightRule(Student student){
        this.student = student;
    }
}
//创建规则5-身高
HeightRule rule5 = new HeightRule(student);
  1. 创建实例(fact)
java 复制代码
 //创建一个Student实例(Fact)
  Student student = new Student(3,"girl",9,true, 160,true);
  Facts facts = new Facts();
  facts.put("student", student);
  1. 创建引擎,并执行规则
java 复制代码
//注册规则
Rules rules = new Rules();
rules.register(rule1);
rules.register(rule2);
//rules.register(rule3);
rules.register(rule4);
rules.register(rule5);

//创建规则执行引擎,并执行规则
RulesEngine rulesEngine = new DefaultRulesEngine();
System.out.println("开始判断是否是一个篮球苗子:" + JSON.toJSONString(student));
rulesEngine.fire(rules, facts);
System.out.println("是否为好苗子:" + student.getIsGoodSeed());
  1. 执行结果

4.4 商机流转如何接入easyRule

熟悉了easyRule如何使用的,接下来看看我们如何在项目中落地的,我们分了几步:

  1. easyRule工具包进行二次改装,使其执行规则后能有返回值,封装成jar包,将规则引擎抽取成通用能力。
    • 初始化规则相关配置(首次初始化+定时更新);
    • 提供对外public T fire(String ruleId, V v)通用的规则引擎api接口。 这里我们将规则引擎的处理结果进行了返回,因为业务上很多场景需要,比如不符合规则时的提醒文案。
  2. 将现有的流转规则进行整理提取,将各种判断条件拆解成单一的指标判断。
  3. 项目中引入easyRule工具。
    • 项目中配置规则引擎相关配置;
    • 实例化RuleEngineTemplate类;
    • 根据场景,组装上下文context
    • 调用ruleEngineTemplate.fire(ruleId,context)方法。

引入后,我们的商机流转流程发生了如下变化:

4.5 商机解绑流程举例

  1. 商机解绑流程
  2. 解绑对应的规则配置application.yml
yml 复制代码
spring:
  easy-rule:
    priority-threshold: 100
    skip-on-first-failed-rule: false
    skip-on-first-applied-rule: true
    skip-on-first-non-triggered-rule: false
    rules:
      - rule-id: "opportunity_unbind"
        rule-file-location: "opportunity_unbind" #规则配置文件
        rule-config-type: JSON
        rule-factory-type: SPEL

具体的规则配置json

json 复制代码
[
      {
        "name": "bind_check_cate",
        "description": "判断是否冻结-72小时",
        "condition": "@opportunityUnbindRuleBll.checkOpportunityNeedFreeze(#context.getOpportunityId(), n,m)",
        "priority": 4,
        "actions": [
          "@clueOpporBll.unbindOpportunity(#context,T(OpportunityStatusEnum).UNBIND, T(com.clue.enums.OpportunityMinorStatusEnum).UNBIND_FROZEN)"
        ]
      },
      {
        "name": "task_bind_out",
        "description": "任务商机流回公海",
        "condition": "#context.getOpportunityStatus() == T(com.enums.OpportunityStatusEnum).TASK && #context.getOperationTypeEnum() == T(com.OpportunityOperationTypeEnum).TASK_BACK_PUBLIC",
        "priority": 5,
        "actions": [
          "@clueBll.unbindOpportunity(#context,T(com.zhuanzhuan.biz.clue.enums.OpportunityStatusEnum).UNBIND, T(com.OpportunityMinorStatusEnum).UNBIND_NORMAL)"
        ]
      },
      {
        "name": "unbind_operate",
        "description": "判断解绑后去向,现阶段全部回到公海",
        "condition": "true",
        "priority": 10,
        "actions": [
          "@clueOpportunityBll.unbindOpportunity(#context,T(com.OpportunityStatusEnum).UNBIND, T(com.enums.OpportunityMinorStatusEnum).UNBIND_NORMAL)"
        ]
      }
    ]
  }
]
  1. 业务代码
java 复制代码
public Result<ParallelExecuteDTO> unbindOpportunity(UnbindOpportunityRequest request) {
  return parallelExecutor.parallelExecute(request.getOpportunityIds(), (Long opportunityId) -> {
            final Result<String> unbindResult = opportunityUnbindRuleBll.unbindOpportunity(opportunityId, request.getOperator(),
                  request.getReasonType(), request.getReasonDesc(), request.getOperationType());
            logger.info("method=unbindOpportunity, act=unbind, opportunityId={},unbindResult={}", opportunityId, unbindResult);
            return unbindResult;
            }
        );
    }

4.6 引入规则引擎前后效果对比

五、总结

easyRule引入商机流转业务过程中,从调研到选型再到最终落地,遇到了各种大大小小的问题,但最终的效果还是比较明显的,对团队的整体效率提升非常明显,这里有几点总结与建议与大家分享。

  1. 系统引入规则引擎,一定要场景符合,不能为了引入而引入;
  2. 业务规则转换为抽象的规则配置,可以多和业务人员交流,他们对于规则的理解可能更深刻;
  3. 选择规则引擎方案后,需要定好规则维护规范,后续执行按照规范维护;
  4. 对于集群中没有直接引用的代码,不要直接清理,有可能是在规则文件里有引用。

作者介绍

杨迎,转转商业后端开发工程师,目前负责商业B端相关业务系统开发(商机线索、客户运营、销售运营管理、广告发布等)。

转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。`
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~`

相关推荐
新手小袁_J几秒前
JDK11下载安装和配置超详细过程
java·spring cloud·jdk·maven·mybatis·jdk11
呆呆小雅1 分钟前
C#关键字volatile
java·redis·c#
Monly212 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
Ttang234 分钟前
Tomcat原理(6)——tomcat完整实现
java·tomcat
goTsHgo5 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
钱多多_qdd15 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
waicsdn_haha17 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
Q_192849990627 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
Code_流苏29 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
良许Linux31 分钟前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网