drools规则引擎的在项目中的使用手记

 需求 按照登录用户的会员等级 和签到周期 根据一定的计算规则送积分。由于之前都是通过if else去做的控制。规则变更的时候可能需要重新调整代码甚至发布服务。

由于不想再每次规则变更后需要调整代码,于是最近在确认方案, 于是最好找到了规则引擎。

什么是规则引擎

规则引擎,全称为业务规则管理系统,英文名为BRMS(即Business Rule Management System)。规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模块编写业务决策(业务规则),由用户或开发者在需要时进行配置、管理。

需要注意的是规则引擎并不是一个具体的技术框架,而是指的一类系统,即业务规则管理系统。目前市面上具体的规则引擎产品有:drools(最活跃的开源规则引擎)、VisualRules(旗正规则引擎)国内商业规则引擎品牌、iLog JRules(商用BRMS)等。

规则引擎实现了将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台

 系统中引入规则引擎后,业务规则不再以程序代码的形式驻留在系统中,取而代之的是处理规则的规则引擎,业务规则存储在规则库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。业务规则被加载到规则引擎中供应用系统调用

使用规则引擎好处:

1、业务规则与系统代码分离,实现业务规则的集中管理

2、在不重启服务的情况下可随时对业务规则进行扩展和维护

3、可以动态修改业务规则,从而快速响应需求变更

4、规则引擎是相对独立的,只关心业务规则,使得业务分析人员也可以参与编辑、维护系统的业务规则

5、减少了硬编码业务规则的成本和风险

6、使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得的简单

Drools介绍

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

drools官网地址:https://drools.org/

文档地址: https://docs.jboss.org/drools/release/6.5.0.Final/drools-docs/html_single/index.html (就是打开太慢)

介绍信息:https://docs.jboss.org/drools/release/7.58.0.Final/drools-docs/html_single/index.html#decision-engine-con_decision-engine


drools源码下载地址:https://github.com/kiegroup/drools

以上都是介绍drools 中的信息。

在项目中的使用,由于我目前项目都是基于springboot开发,所以提供的代码示例都是基于springboot版本的。

规则引擎api 调用流程:

1.引入依赖

<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-core</artifactId>
			<version>7.10.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-compiler</artifactId>
			<version>7.10.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.kie</groupId>
			<artifactId>kie-api</artifactId>
			<version>7.10.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.kie</groupId>
			<artifactId>kie-spring</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-tx</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-beans</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-core</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework</groupId>
					<artifactId>spring-context</artifactId>
				</exclusion>
			</exclusions>
			<version>7.10.0.Final</version>
		</dependency>

2.在resources目录下 新建 rules 文件夹 存放规则文件的(建议idea里面装dools插件)

新建一个 resources/rules/bookrule.drl 文件

// 规则引擎代码中注释 只能 使用 // 或者 /* */ 这两种 ,不支持#这种

//图书优惠规则
package book.discount
import com.example.edwin.po.Order



//规则一:所购图书总价在100元以下的没有优惠
rule "book_discount_1"   // 这个 book_discount_1 名称要唯一 
    // salience 10   优先级 数字越大优先级越高
    when
        $order:Order(originalPrice < 100)
    then
        $order.setRealPrice($order.getOriginalPrice());
        System.out.println("成功匹配到规则一:所购图书总价在100元以下的没有优惠");
end

//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
    when
        $order:Order(originalPrice < 200 && originalPrice >= 100)
    then

        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end

//规则三:所购图书总价在200到300元的优惠50元
rule "book_discount_3"
    when
        $order:Order(originalPrice <= 300 && originalPrice >= 200)
    then
        $order.setRealPrice($order.getOriginalPrice() - 50);
        System.out.println("成功匹配到规则三:所购图书总价在200到300元的优惠50元");
end

//规则四:所购图书总价在300元以上的优惠100元
rule "book_discount_4"
    when
        $order:Order(originalPrice >= 300)
    then
        $order.setRealPrice($order.getOriginalPrice() - 100);
        System.out.println("成功匹配到规则四:所购图书总价在300元以上的优惠100元");
end
@Data
public class Order implements Serializable {
    //订单原始价格,即优惠前价格
    private Double originalPrice;
    //订单真实价格,即优惠后价格
    private Double realPrice;
}



	@Test
	void contextLoads() {
		KieServices kieServices = KieServices.Factory.get();
		KieContainer container = kieServices.getKieClasspathContainer();
		//会话对象,用于和规则引擎交互
		KieSession kieSession = container.newKieSession();

		//构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格
		Order order = new Order();
		order.setOriginalPrice(190d);

		//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
		kieSession.insert(order);

		//激活规则引擎,如果规则匹配成功则执行规则
		int allRules = kieSession.fireAllRules();

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

		System.out.println("allRules : " + allRules);

		System.out.println("order: " +order);

	}

通过上面的demo可以发现,使用drools规则引擎主要工作就是编写规则文件,
在规则文件中定义跟业务相关的业务规则,例如本案例定义的就是优惠规则。
规则定义好后就需要调用drools提供的API将数据提供给规则引擎进行规则模式匹配,
引擎会执行匹配成功的规则并将计算的结果返回给我们。

可能大家会有疑问,就是我们虽然没有在代码中编写规则的判断逻辑,但是我们
还是在规则文件中编写了业务规则,这跟在代码中编写规则有什么本质的区别呢?

我们前面其实已经提到,使用规则引擎时业务规则可以做到动态管理。业务人员
可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交
业务规则等。这样就可以做到在不重启服务的情况下调整业务规则

规则引擎构成

drools规则引擎由以下三部分构成:

  • Working Memory(工作内存)
  • Rule Base(规则库)
  • Inference Engine(推理引擎)

其中Inference Engine(推理引擎)又包括:

  • Pattern Matcher(匹配器)
  • Agenda(议程)
  • Execution Engine(执行引擎)

如下图所示:

3.3.2 相关概念说明

Working Memory:工作内存,drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,所以我们开发的应用程序只需要将我们的数据插入到Working Memory中即可,例如本案例中我们调用kieSession.insert(order)就是将order对象插入到了工作内存中。

Fact:事实,是指在drools 规则应用当中,将一个普通的JavaBean插入到Working Memory后的对象就是Fact对象,例如本案例中的Order对象就属于Fact对象。Fact对象是我们的应用和规则引擎进行数据交互的桥梁或通道。

Rule Base:规则库,我们在规则文件中定义的规则都会被加载到规则库中。

Pattern Matcher:匹配器,将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,匹配成功的规则将被激活并放入Agenda中。

Agenda:议程,用于存放通过匹配器进行模式匹配后被激活的规则。

Execution Engine:执行引擎,执行Agenda中被激活的规则。

3.3.3 规则引擎执行过程

3.3.4 KIE介绍

我们在操作Drools时经常使用的API以及它们之间的关系如下图:

通过上面的核心API可以发现,大部分类名都是以Kie开头。Kie全称为Knowledge Is Everything,即"知识就是一切"的缩写,是Jboss一系列项目的总称。如下图所示,Kie的主要模块有OptaPlanner、Drools、UberFire、jBPM。

规则文件构成

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

drl就是Drools Rule Language。在规则文件中编写具体的规则内容
一套完整的规则文件的内容如:

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

语法:

rule "ruleName"
    attributes
    when
        LHS
    then
        RHS
end

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

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

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

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

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

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

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

在新建一个 userRule.drl

package user
import com.example.edwin.po.UserInfo
import com.example.edwin.util.Resp
// 全局应用需要在 kieSession 中设置 global 属性 如果不设置 UserService 是获取不到的
// 除非 UserService 是个公共配置类,调用的方法是静态方法
global com.example.edwin.service.UserService userService

rule "rule_user_1"
//   no-loop    防止死循环  可以根据具体的规则 搭配使用
//    salience 10	//指定规则执行优先级
//    dialect java	 //指定规则使用的语言类型,取值为java和mvel
//    enabled false	 //指定规则是否启用
//    activation-group  “my-test-group”	//激活分组,具有相同分组名称的规则只能有一个规则触发
    when
       $user:UserInfo(id matches "1" && name != "")
    then
       userService.insertInfo($user);
      $user.setId("15");
//      update($user);  将内存中的数据 更新  会出发规则 重新选择 建议搭配  no-loop使用,或者在写的时候一定注意 规则条件 避免死循环
//      insert($user);  将内存中的数据 插入  会出发规则 重新选择
//      retract($user)  将内存中的数据 清除 会出发规则 重新选择
      System.out.println("匹配到1:"+ $user);
end

rule "rule_user_2"
   when
       $user:UserInfo(id =='10' && name contains 'yu')
      // 返回值设置
       $resp:Resp()
   then
     // 先插入数据库
      userService.insertInfo($user);
      $resp.setCode(200);
      $resp.setData($user);
      $resp.setMsg("操作成功");
       System.out.println("匹配到2:"+ $user);
end

rule "rule_user_3"
    when
       $user:UserInfo(id matches "15" && name != "")
    then
      userService.insertInfo($user);
      System.out.println("匹配到3:"+ $user);
end

其他代码类:

@Data
public class UserInfo implements Serializable {

    private String id;

    private String name;
}


@Service
public class UserService {

    public void insertInfo(UserInfo userInfo){
        System.out.println("insert success!"+ userInfo.toString());
//        test();
    }


    private void test(){
        throw new RuntimeException("error !");
    }
}


@Data
public class Resp implements Serializable {

    private Integer code;

    private Object data;

    private String msg;
}


@Test
	void UserTest() {
		KieServices kieServices = KieServices.Factory.get();
		KieContainer container = kieServices.getKieClasspathContainer();
		//会话对象,用于和规则引擎交互
		KieSession kieSession = container.newKieSession();

		//构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格
		UserInfo user = new UserInfo();
		user.setId("1");
		user.setName("yulang你好");

		//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
		kieSession.insert(user);

		kieSession.setGlobal("userService",userService);

		Resp resp = new Resp();
		kieSession.insert(resp);

		//激活规则引擎,如果规则匹配成功则执行规则
		int allRules = kieSession.fireAllRules();

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

		System.out.println("allRules : " + allRules);

		System.out.println("order: " +user);
		System.out.println("resp: " +resp);

	}

Drools提供的比较操作符,如下表:

符号说明
>大于
<小于
>=大于等于
<=小于等于
==等于
!=不等于
contains检查一个Fact对象的某个属性值是否包含一个指定的对象值
not contains检查一个Fact对象的某个属性值是否不包含一个指定的对象值
memberOf判断一个Fact对象的某个属性是否在一个或多个集合中
not memberOf判断一个Fact对象的某个属性是否不在一个或多个集合中
matches判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
not matches判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配

一般我自己常用前面8种 ,

只要按照规则来编写规则文件,其实套路都是一只的,只需要自己按照要求编写自己的规则类。

我在项目中采用的是动态加载规则类的方式,只需要在页面上调整下规则,新的规则自动发布,不需要重启服务等。

具体实现思路如下:

1、将规则文件的内容存储在数据库中

2、Drools相关对象(例如KieContainer对象)的创建都基于数据库中存储的规则来创建

3、提供HTTP访问接口,当规则发生变化时调用此接口重新加载数据库中的规则,重新创建KieContainer等对象

实现步骤及代码:

1.数据库创建一张规则表:
CREATE TABLE `rule`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `create_time` TIMESTAMP  NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `last_modify_time` TIMESTAMP not NULL DEFAULT CURRENT_TIMESTAMP,
  `rule_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `version` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `UK_9yepjak9olg92holwkr8p3l0f`(`rule_key`) USING BTREE,
  UNIQUE INDEX `UK_ilmbp99kyt6gy10224pc9bl6n`(`version`) USING BTREE,
  UNIQUE INDEX `UK_ei48upwykmhx9r5p7p4ndxvgn`(`last_modify_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

2. 将规则数据插入表中:
INSERT INTO `hosp`.`rule` (`id`, `content`, `create_time`, `last_modify_time`, `rule_key`, `version`) VALUES (1, 'package user.resp\r\nimport com.example.edwin.po.UserInfo\r\nimport com.example.edwin.util.Resp\r\nglobal com.example.edwin.service.UserService userService\r\n\r\nrule \"resp_user_1\"\r\n    when\r\n       $user:UserInfo(id matches \"1\" && name != \"\")\r\n    then\r\n      userService.insertInfo($user);\r\n      $user.setId(\"15\");\r\n      System.out.println(\"匹配到1:\"+ $user);\r\nend\r\n\r\nrule \"resp_user_2\"\r\n   when\r\n       $user:UserInfo(id ==\'10\' && name contains \'yu\')\r\n       $resp:Resp()\r\n   then\r\n      userService.insertInfo($user);\r\n      $resp.setCode(200);\r\n      $resp.setData($user);\r\n      $resp.setMsg(\"操作成功\");\r\n       System.out.println(\"匹配到2:\"+ $user);\r\nend\r\n\r\nrule \"resp_user_3\"\r\n    when\r\n       $user:UserInfo(id matches \"15\" && name != \"\")\r\n    then\r\n      userService.insertInfo($user);\r\n      System.out.println(\"匹配到3:\"+ $user);\r\nend\r\n', now(), now(), 'score', '1');
INSERT INTO `hosp`.`rule` (`id`, `content`, `create_time`, `last_modify_time`, `rule_key`, `version`) VALUES (2, '\r\n//图书优惠规则\r\npackage book.discount\r\nimport com.example.edwin.po.Order\r\n\r\n//规则一:所购图书总价在100元以下的没有优惠\r\nrule \"book_discount_1\"\r\n  // salience 10   优先级 数字越大优先级越高\r\n    when\r\n        $order:Order(originalPrice < 100)\r\n    then\r\n        $order.setRealPrice($order.getOriginalPrice());\r\n        System.out.println(\"成功匹配到规则一:所购图书总价在100元以下的没有优惠\");\r\nend\r\n\r\n//规则二:所购图书总价在100到200元的优惠20元\r\nrule \"book_discount_2\"\r\n    when\r\n        $order:Order(originalPrice < 200 && originalPrice >= 100)\r\n    then\r\n//        System.out.println(\"$op:\"+$op);\r\n        $order.setRealPrice($order.getOriginalPrice() - 20);\r\n        System.out.println(\"成功匹配到规则二:所购图书总价在100到200元的优惠20元\");\r\nend\r\n\r\n//规则三:所购图书总价在200到300元的优惠50元\r\nrule \"book_discount_3\"\r\n    when\r\n        $order:Order(originalPrice <= 300 && originalPrice >= 200)\r\n    then\r\n        $order.setRealPrice($order.getOriginalPrice() - 50);\r\n        System.out.println(\"成功匹配到规则三:所购图书总价在200到300元的优惠50元\");\r\nend\r\n\r\n//规则四:所购图书总价在300元以上的优惠100元\r\nrule \"book_discount_4\"\r\n    when\r\n        $order:Order(originalPrice >= 300)\r\n    then\r\n        $order.setRealPrice($order.getOriginalPrice() - 100);\r\n        System.out.println(\"成功匹配到规则四:所购图书总价在300元以上的优惠100元\");\r\nend', now(), now(), 'book_money', '2');

3. 实体类

@Entity
@Data
@Table(name = "rule")
public class Rule implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)//主键生成策略
    @Column(name="id")//数据库字段名
    private Long id;
    @Column(name="rule_key")
    private String ruleKey;
    @Column(name="content")
    private String content;
    @Column(name="version")
    private String version;
    @Column(name="last_modify_time")
    private String lastModifyTime;
    @Column(name="create_time")
    private String createTime;
}

4. dao层
@Repository
public interface RuleRepository extends JpaRepository<Rule,Long>{
}



   5. 动态加载规则, 我这里是默认启动加载全部的规则。也可以在规则变更的时候重新加载

   @Autowired
    private RuleRepository ruleRepository;
    
    @Autowired
    public void list(){
        List<Rule> all = ruleRepository.findAll();

        KieServices kieServicesFactory = KieServices.Factory.get();
        KieRepository repository = kieServicesFactory.getRepository();
        KieFileSystem kieFileSystem = kieServicesFactory.newKieFileSystem();
        for(Rule rule : all){
            String drl = rule.getContent();
            System.out.println(drl);
            kieFileSystem.write("src/main/resources/" + drl.hashCode() + ".drl", drl);
        }
        KieBuilder kieBuilder = kieServicesFactory.newKieBuilder(kieFileSystem);
         kieBuilder.buildAll();

        if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) {
            throw new RuntimeException("Build Errors:\n" + kieBuilder.getResults().toString());
        }

        KieContainer kContainer = kieServicesFactory.newKieContainer(repository.getDefaultReleaseId());
        this.kieContainer = kContainer;
    }


6. 业务调用规则
    @GetMapping("/test")
    public String test() {
        KieSession kieSession = kieContainer.newKieSession();

        //构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格
        UserInfo user = new UserInfo();
        user.setId("10");
        user.setName("yulang你好");

        //将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
        kieSession.insert(user);

        kieSession.setGlobal("userService",userService);

        Resp resp = new Resp();
        kieSession.insert(resp);

        //激活规则引擎,如果规则匹配成功则执行规则
        int allRules = kieSession.fireAllRules();
        System.out.println("触发了" + allRules + "条规则");
        //关闭会话
        kieSession.dispose();

        return "success" + resp.toString();
    }

所有的demo代码地址项目:链接:https://pan.baidu.com/s/1k4H60031EhCtG-2L-k2VFQ 
提取码:rfv2

drools 里面还有很多功能细节,感兴趣的朋友可以看下官网研究下。另外Kie好像还有个在线编辑的工具 WorkBench,可以根据自己情况进行学习。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值