美文网首页
SpringBoot整合规则引擎Drools

SpringBoot整合规则引擎Drools

作者: 上善若泪 | 来源:发表于2023-05-10 09:11 被阅读0次

    1 整合规则引擎Drools

    1.1 前言

    假如有这么个需求,网上购物,需要根据不同的规则计算商品折扣,比如VIP客户增加5%的折扣,购买金额超过1000元的增加10%的折扣等,而且这些规则可能随时发生变化,甚至增加新的规则。面对这个需求,你该怎么实现呢?难道是计算规则一变,就要修改业务代码,重新测试,上线吗。

    其实,我们可以通过规则引擎来实现,Drools 就是一个开源的业务规则引擎,可以很容易地与 springboot 应用程序集成,那本文就用 Drools 来实现一下上面说的需求吧。

    1.2 pom.xml

    我们创建一个spring boot应用程序,pom中添加drools相关的依赖,如下:

    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-core</artifactId>
      <version>7.59.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-compiler</artifactId>
      <version>7.59.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-decisiontables</artifactId>
      <version>7.59.0.Final</version>
    </dependency>
    

    1.3 Drools配置类

    创建一个名为 DroolsConfig 的配置 java 类。

    @Configuration
    public class DroolsConfig {
        // 制定规则文件的路径
        private static final String RULES_CUSTOMER_RULES_DRL = "rules/customer-discount.drl";
        private static final KieServices kieServices = KieServices.Factory.get();
    
        @Bean
        public KieContainer kieContainer() {
            KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
            KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
            kb.buildAll();
            KieModule kieModule = kb.getKieModule();
            KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
            return kieContainer;
        }
    }
    

    解析说明:

    • 定义了一个 KieContainerSpringBeanKieContainer 用于通过加载应用程序的 /resources 文件夹下的规则文件来构建规则引擎。
    • 创建 KieFileSystem 实例并配置规则引擎并从应用程序的资源目录加载规则的 DRL 文件。
    • 使用 KieBuilder 实例来构建 drools 模块。我们可以使用 KieSerive 单例实例来创建 KieBuilder 实例。
    • 最后,使用 KieService 创建一个 KieContainer 并将其配置为 spring bean

    1.4 示例Demo

    1.4.1 添加业务Model

    创建一个订单对象 OrderRequest,这个类中的字段后续回作为输入信息发送给定义的drools规则中,用来计算给定客户订单的折扣金额。

    @Data
    public class OrderRequest {
        /**
         * 客户号
         */
        private String customerNumber;
        /**
         * 年龄
         */
        private Integer age;
        /**
         * 订单金额
         */
        private Integer amount;
        /**
         * 客户类型
         */
        private CustomerType customerType;
    }
    

    定义一个客户类型CustomerType 的枚举,规则引擎会根据该值计算客户订单折扣百分比,如下所示。

    public enum CustomerType {
        LOYAL, NEW, DISSATISFIED;
    
        public String getValue() {
            return this.toString();
        }
    }
    

    最后,创建一个订单折扣类 OrderDiscount ,用来表示计算得到的最终的折扣,如下所示。

    @Data
    public class OrderDiscount {
    
        /**
         * 折扣
         */
        private Integer discount = 0;
    }
    

    我们将使用上述响应对象返回计算出的折扣

    1.4.2 定义drools 规则

    前面的 DroolsConfig 类中指定 drools 规则的目录,现在我们在 /src/main/resources/rules 目录下添加customer-discount.drl 文件,在里面定义对应的规则。

    完整的规则源码如下:

    import com.alvin.drools.model.OrderRequest;
    import com.alvin.drools.model.CustomerType;
    global com.alvin.drools.model.OrderDiscount orderDiscount;
    
    dialect "mvel"
    
    // 规则1: 根据年龄判断
    rule "Age based discount"
        when
            // 当客户年龄在20岁以下或者50岁以上
            OrderRequest(age < 20 || age > 50)
        then
            // 则添加10%的折扣
            System.out.println("==========Adding 10% discount for Kids/ senior customer=============");
            orderDiscount.setDiscount(orderDiscount.getDiscount() + 10);
    end
    
    // 规则2: 根据客户类型的规则
    rule "Customer type based discount - Loyal customer"
        when
            // 当客户类型是LOYAL
            OrderRequest(customerType.getValue == "LOYAL")
        then
            // 则增加5%的折扣
            System.out.println("==========Adding 5% discount for LOYAL customer=============");
            orderDiscount.setDiscount(orderDiscount.getDiscount() + 5);
    end
    
    rule "Customer type based discount - others"
        when
        OrderRequest(customerType.getValue != "LOYAL")
    then
        System.out.println("==========Adding 3% discount for NEW or DISSATISFIED customer=============");
        orderDiscount.setDiscount(orderDiscount.getDiscount() + 3);
    end
    
    rule "Amount based discount"
        when
            OrderRequest(amount > 1000L)
        then
            System.out.println("==========Adding 5% discount for amount more than 1000$=============");
        orderDiscount.setDiscount(orderDiscount.getDiscount() + 5);
    end
    

    解析说明:

    • 我们使用了一个名为 orderDiscount 的全局参数,可以在多个规则之间共享。
    • drl 文件可以包含一个或多个规则。我们可以使用 mvel 语法来指定规则。此外,每个规则使用rule关键字进行描述。
    • 每个规则 when-then 语法来定义规则的条件。
    • 根据订单请求的输入值,我们正在为结果添加折扣。如果规则表达式匹配,每个规则都会向全局结果变量添加额外的折扣

    1.4.3 添加Service层

    创建一个名为OrderDiscountService 的服务类,如下:。

    @Service
    public class OrderDiscountService {
    
        @Autowired
        private KieContainer kieContainer;
    
        public OrderDiscount getDiscount(OrderRequest orderRequest) {
            OrderDiscount orderDiscount = new OrderDiscount();
            // 开启会话
            KieSession kieSession = kieContainer.newKieSession();
            // 设置折扣对象
            kieSession.setGlobal("orderDiscount", orderDiscount);
            // 设置订单对象
            kieSession.insert(orderRequest);
            // 触发规则
            kieSession.fireAllRules();
            //或者  通过规则过滤器实现只执行指定规则
            //kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("Age based discount"));
            // 中止会话
            kieSession.dispose();
            return orderDiscount;
        }
    }
    

    解析说明:

    • 注入KieContainer 实例并创建一个KieSession 实例。
    • 设置了一个OrderDiscount 类型的全局参数,它将保存规则执行结果。
    • 使用 insert() 方法将请求对象传递给 drl 文件。
    • 调用 fireAllRules()方法触发所有规则。
    • 最后通过调用KieSessiondispose()方法终止会话。

    1.4.4 添加Controller

    创建一个名为OrderDiscountController 的Controller类,具体代码如下:

    @RestController
    public class OrderDiscountController {
    
        @Autowired
        private OrderDiscountService orderDiscountService;
    
        @PostMapping("/get-discount")
        public ResponseEntity<OrderDiscount> getDiscount(@RequestBody OrderRequest orderRequest) {
            OrderDiscount discount = orderDiscountService.getDiscount(orderRequest);
            return new ResponseEntity<>(discount, HttpStatus.OK);
        }
    }
    

    1.4.5 测试

    对于年龄 < 20 且金额 > 1000 的 LOYAL 客户类型,我们应该根据我们定义的规则获得 20% 的折扣。

    入参:
    {
        "customerNumber":"123456",
        "age":20,
        "amount":20000,
        "customerType":"LOYAL"
    }
    出参:
    {
        "discount": 10
    }
    

    参考链接:https://mp.weixin.qq.com/s/SfMhb34dj7DrLvMCKZv9Uw

    1.5 drools规则解析

    1.5.1 简介

    在使用 Drools 时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drldrlDrools Rule Language的缩写。在规则文件中编写具体的规则内容。
    一套完整的规则文件内容构成如下:

    关键字 描述
    package 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用
    import 用于导入类或者静态方法
    global 全局变量
    function 自定义函数
    query 查询
    rule end 规则体

    Drools支持的规则文件,除了drl形式,还有Excel文件类型的。

    1.5.2 规则体语法结构

    规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。
    规则体语法结构如下:

    rule "ruleName"
        attributes
        when
            LHS
        then
            RHS
    end
    

    解析说明:

    • rule:关键字,表示规则开始,参数为规则的唯一名称。
    • attributes:规则属性,是rulewhen之间的参数,为可选项。
    • when:关键字,后面跟规则的条件部分。
    • LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。
      还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,也可以不写,默认连接为and
    • then:关键字,后面跟规则的结果部分。
    • RHS(Right Hand Side):是规则的后果或行动部分的通用名称。
    • end:关键字,表示一个规则结束

    1.5.3 注释

    drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。
    单行注释用//进行标记,多行注释以/开始,以/结束。如下示例:

    //规则rule1的注释,这是一个单行注释
    rule "rule1"
        when
        then
            System.out.println("rule1触发");
    end
    
    /*
    规则rule2的注释,
    这是一个多行注释
    */
    rule "rule2"
        when
        then
            System.out.println("rule2触发");
    end
    

    1.5.4 Pattern模式匹配

    前面我们已经知道了 Drools 中的匹配器可以将 Rule Base 中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。

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

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

    //所购图书总价在100到200元的优惠20元
    rule "book_discount_2"
        when
            //Order为类型约束,originalPrice为属性约束
            $order:Order(originalPrice < 200 && originalPrice >= 100)
        then
            $order.setRealPrice($order.getOriginalPrice() - 20);
            System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
    end
    
    //规则二:所购图书总价在100到200元的优惠20元
    rule "book_discount_2"
        when
            $order:Order($op:originalPrice < 200 && originalPrice >= 100)
        then
            System.out.println("$op=" + $op);
            $order.setRealPrice($order.getOriginalPrice() - 20);
            System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
    end
    

    LHS部分还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,也可以不写,默认连接为and

    //规则二:所购图书总价在100到200元的优惠20元
    rule "book_discount_2"
        when
            $order:Order($op:originalPrice < 200 && originalPrice >= 100) and
            $customer:Customer(age > 20 && gender=='male')
        then
            System.out.println("$op=" + $op);
            $order.setRealPrice($order.getOriginalPrice() - 20);
            System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
    end
    

    1.5.5 比较操作符

    Drools提供的比较操作符有:>、<、>=、<=、==、!=、contains 、not contains、memberOf 、not memberOf、matches 、not matches
    前6个比较操作符和Java中的完全相同,下面我们重点学习后6个比较操作符。

    • contains | not contains 语法结构
      Object(Field[Collection/Array] contains value)
      Object(Field[Collection/Array] not contains value)
    • memberOf | not memberOf 语法结构
      Object(field memberOf value[Collection/Array])
      Object(field not memberOf value[Collection/Array])
    • matches | not matches 语法结构
      Object(field matches “正则表达式”)
      Object(field not matches “正则表达式”)

    1.5.6 dialect 属性

    drools 支持两种 dialectjavamvel

    • dialect:缺省为 java,当然我们也推荐统一使用java dialect, 以降低维护成本.
    • dialect:属性仅用于设定 RHS 部分语法,LHS 部分并不受 dialect 的影响.

    package 和 rule 都可以指定 dialect 属性.

    mvel 是一种表达式语言, github主页为 https://github.com/mvel/mvel , 文档主页为 http://mvel.documentnode.com/
    dools 中的 mvel dialect 可以认为是 java dialect 的超集, 也就是说 mvel dialect 模式下, 也支持 java dialect 的写法
    mveljava 的主要区别:

    • 对于POJO 对象, java dialect 必须使用 gettersetter 方法.
    • 对于POJO 对象, mvel dialect 可以直接使用属性名称进行读写, 甚至是 private 属性也可以.

    java dialect示例:

    rule "java_rule"  
       enabled true
       dialect "java"
       when
           $order:Order()
       then
          System.out.println("java_rule fired");
          $order.setRealPrice($order.getOriginalPrice()*0.8) ;
    end
    

    mvel dialect示例:

    rule "mvel_rule"
       enabled false
       dialect "mvel"
       when
           $order:Order()
       then
          System.out.println("mvel_rule fired");
          $order.realPrice=$order.originalPrice*0.7 ;   
    end
    

    相关文章

      网友评论

          本文标题:SpringBoot整合规则引擎Drools

          本文链接:https://www.haomeiwen.com/subject/ssgdsdtx.html