规则引擎概述

规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模板编写业务决策(业务规则),由用户或开发者在需要时进行配置、管理。


使用场景

比如商城购物,满300减100,满500减200等等,而且这些规则有可能随时会变动的。如果实现这个需求,正常情况下我们怎么做呢?

if…else

伪代码

if(amount >= 300) {
    amount -= 100;
} else if(amount >= 500) {
    amount -= 200;
}

策略模式

伪代码

interface Strategy {
    reductionAmount(int amount);
}

class Strategy1 {
    reductionAmount(int amount);
}

class Strategy2 {
    reductionAmount(int amount);
}

以上方式都可以实现功能,但是如果折扣经常变动呢?不同的商品有不同的规则呢?而且这些规则与代码严重耦合,每次规则变动都需要重新测试,部署。

Drools介绍

drools是一款由JBoss组织提供的基于java语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中(如存放在数据库中),使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。

springboot集成drools案例

业务场景

订单金额

优惠金额

100-200

10

200-500

20

500-1000

50

1000-2000

100

2000-5000

300

5000-10000

500

10000以上

1000

创建springboot项目

创建maven工程并导入drools相关依赖

<!-- https://mvnrepository.com/artifact/org.drools/drools-compiler -->  
<dependency>  
    <groupId>org.drools</groupId>  
    <artifactId>drools-compiler</artifactId>  
    <version>7.73.0.Final</version>  
</dependency>  
<!-- https://mvnrepository.com/artifact/org.drools/drools-mvel -->  
<dependency>  
    <groupId>org.drools</groupId>  
    <artifactId>drools-mvel</artifactId>  
    <version>7.73.0.Final</version>  
</dependency>
<!-- 测试 -->
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-test</artifactId>  
</dependency>

resources/META-INF/kmodule.xml

<?xml version="1.0" encoding="UTF-8"?>  
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">  
  
    <!--  
    name:指定kbase的名称,可以任意,但是需要唯一  
    packages:指定规则文件的目录,需要根据实际情况填写,否则无法加载到规则文件  
    default:指定当前kbase是否为默认  
    -->  
    <kbase name="fdf" packages="com.fandf.rules" default="true">  
        <!--  
        name:指定ksession的名称,可以任意,但需要唯一  
        default:指定当前session是否为默认  
        -->  
        <ksession name="fdf-rule" default="true"/>  
    </kbase>  
</kmodule>

创建实体order

package com.fandf.test.entity;  
  
import lombok.Data;  
  
import java.math.BigDecimal;  
  
/**  
* @author fandongfeng  
* @date 2023/5/3 19:17  
*/  
@Data  
public class Order {  
  
  
    /**  
    * 订单优惠前价格  
    */  
    private BigDecimal originalPrice;  
    /**  
    * 订单优惠后价格  
    */  
    private BigDecimal realPrice;  
  
}

创建规则文件resources/rules/orderDiscount.drl

// 订单优惠规则 须与kmodule.xml的package一致  
package com.fandf.rules  
import com.fandf.test.entity.Order  
import java.math.BigDecimal  
  
// 规则一:100-200 优惠 10  
rule "order_discount_1"  
    when  
        $order: Order(originalPrice >= 100 && originalPrice < 200) // 匹配模式,到规则引擎中(工作内存)查找Order对象,命名为$order  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(10)));  
        System.out.println("成功匹配到规则一,订单金额优惠10元");  
end  
  
// 规则二:200-500 优惠 20  
rule "order_discount_2"  
    when  
        $order: Order(originalPrice >= 200 && originalPrice < 500)  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(20)));  
        System.out.println("成功匹配到规则二,订单金额优惠20元");  
end  
  
// 规则三:500-1000 优惠 50  
rule "order_discount_3"  
    when  
        $order: Order(originalPrice >= 500 && originalPrice < 1000)  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(50)));  
        System.out.println("成功匹配到规则三,订单金额优惠50元");  
end  
// 规则四:1000-2000 优惠 100  
rule "order_discount_4"  
    when  
        $order: Order(originalPrice >= 1000 && originalPrice < 2000)  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(100)));  
        System.out.println("成功匹配到规则四,订单金额优惠100元");  
end  
// 规则五:2000-5000 优惠 300  
rule "order_discount_5"  
    when  
        $order: Order(originalPrice >= 2000 && originalPrice < 5000)  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(300)));  
        System.out.println("成功匹配到规则五,订单金额优惠300元");  
end  
// 规则六:5000-10000 优惠 500  
rule "order_discount_6"  
    when  
        $order: Order(originalPrice >= 5000 && originalPrice < 10000)  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(500)));  
        System.out.println("成功匹配到规则六,订单金额优惠500元");  
end  
// 规则七:10000以上 优惠 1000  
rule "order_discount_7"  
    when  
        $order: Order(originalPrice >= 10000)  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(1000)));  
        System.out.println("成功匹配到规则七,订单金额优惠1000元");  
end

编写测试类OrderTest

package com.fandf.test.entity;  
  
import com.fandf.test.Application;  
import org.junit.jupiter.api.Test;  
import org.kie.api.KieServices;  
import org.kie.api.runtime.KieContainer;  
import org.kie.api.runtime.KieSession;  
import org.springframework.boot.test.context.SpringBootTest;  
  
import java.math.BigDecimal;  
  
@SpringBootTest(classes = Application.class)  
public class OrderTest {  
  
    @Test  
    public void test(){  
        KieServices kieServices = KieServices.Factory.get();  
        // 获取Kie容器对象 默认容器对象  
        KieContainer kieContainer = kieServices.newKieClasspathContainer();  
        // 从Kie容器对象中获取会话对象(默认session对象  
        KieSession kieSession = kieContainer.newKieSession();  

        Order order = new Order();  
        order.setOriginalPrice(BigDecimal.valueOf(180));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  

        kieSession = kieContainer.newKieSession();  
        order.setOriginalPrice(BigDecimal.valueOf(300));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  

        kieSession = kieContainer.newKieSession();  
        order.setOriginalPrice(BigDecimal.valueOf(600));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  

        kieSession = kieContainer.newKieSession();  
        order.setOriginalPrice(BigDecimal.valueOf(1200));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  

        kieSession = kieContainer.newKieSession();  
        order.setOriginalPrice(BigDecimal.valueOf(3000));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  

        kieSession = kieContainer.newKieSession();  
        order.setOriginalPrice(BigDecimal.valueOf(8000));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  

        kieSession = kieContainer.newKieSession();  
        order.setOriginalPrice(BigDecimal.valueOf(12000));  
        // 将order对象插入工作内存  
        kieSession.insert(order);  
        // 匹配对象  
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行  
        kieSession.fireAllRules();  
        System.out.println("优惠前价格:" + order.getOriginalPrice() + ",优惠后价格:" + order.getRealPrice());  



        // 关闭会话  
        kieSession.dispose();  

    }  
  
}

执行输出

[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:03.823 INFO 5536 [-] [main] o.d.c.k.b.impl.ClasspathKieProject       Found kmodule: file:/Users/dongfengfan/IdeaProjects/SpringCloudLearning/fdf-test/target/classes/META-INF/kmodule.xml
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.019 WARN 5536 [-] [main] o.d.c.k.b.impl.ClasspathKieProject       Unable to find pom.properties in /Users/dongfengfan/IdeaProjects/SpringCloudLearning/fdf-test/target/classes
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.024 INFO 5536 [-] [main] o.d.c.k.b.impl.ClasspathKieProject       Recursed up folders, found and used pom.xml /Users/dongfengfan/IdeaProjects/SpringCloudLearning/fdf-test/pom.xml
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.025 INFO 5536 [-] [main] o.d.c.k.b.i.InternalKieModuleProvider    Creating KieModule for artifact com.fandf:fdf-test:1.0.0
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.035 INFO 5536 [-] [main] o.d.c.kie.builder.impl.KieContainerImpl  Start creation of KieBase: fdf
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.041 WARN 5536 [-] [main] o.d.c.kie.builder.impl.KieBuilderImpl    File 'rules/orderDiscount.drl' is in folder 'rules' but declares package 'com.fandf.rules'. It is advised to have a correspondance between package and folder names.
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:05.091 INFO 5536 [-] [main] o.d.c.kie.builder.impl.KieContainerImpl  End creation of KieBase: fdf
成功匹配到规则一,订单金额优惠10元
优惠前价格:180,优惠后价格:170
成功匹配到规则二,订单金额优惠20元
优惠前价格:300,优惠后价格:280
成功匹配到规则三,订单金额优惠50元
优惠前价格:600,优惠后价格:550
成功匹配到规则四,订单金额优惠100元
优惠前价格:1200,优惠后价格:1100
成功匹配到规则五,订单金额优惠300元
优惠前价格:3000,优惠后价格:2700
成功匹配到规则六,订单金额优惠500元
优惠前价格:8000,优惠后价格:7500
成功匹配到规则七,订单金额优惠1000元
优惠前价格:12000,优惠后价格:11000

Drools语法

规则文件

在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。

drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。

关键字

描述

package

包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用

import

用于导入类或静态方法

global

全局变量

function

自定义函数

query

查询

rule end

规则体

规则体语法

规则体是进行业务规则判断、处理业务结果的部分。

rule "ruleName" 
    attributes 
    when 
        LHS
    then 
        RHS
end

rule:关键字,表示规则开始,参数为规则的唯一名称。

attribute:规则属性,是rule与when之间的参数,为可选项。

when:关键字,后面跟规则的条件部分。

LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。

then:关键字,后面跟规则的结果部分。

RHS(Right Hand Side):是规则的后果或行动部分的通用名称。

end:关键字,表示一个规则的结束。

Pattern模式匹配

pattern的语法结构为:绑定变量名:Object(Field约束)

其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。

比如我们的案例

rule "order_discount_1"  
    when  
        $order: Order(originalPrice >= 100 && originalPrice < 200) // 匹配模式,到规则引擎中(工作内存)查找Order对象,命名为$order  
    then  
        $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(10)));  
        System.out.println("成功匹配到规则一,订单金额优惠10元");  
end

符号

说明

>

大于

<

小于

>=

大于等于

<=

小于等于

==

等于

!=

不等于

contains

检查一个Fact对象的某个属性值是否包含一个指定的对象值

not contains

检查一个Fact对象的某个属性值是否不包含一个指定的对象值

memberOf

判断一个Fact对象的某个属性是否在一个或多个集合中

not memberOf

判断一个Fact对象的某个属性是否不在一个或多个集合中

matches

判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配

not matches

判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配