Drools初步使用

19 阅读9分钟
为什么引用规则引擎?
  1. 逻辑与数据分离:数据在域对象中,逻辑在规则中,随着将来的变化,逻辑可以更容易维护,可以将逻辑全部组织在一个或多个非常不同的规则文件中,而不是将逻辑分布在许多域对象或控制器中。

  2. 规则集中管理:通过使用规则,可以创建可执行的规则库,并将这些规则集中编写和管理。如存放到某个目录或者数据库。

  3. 可读性高:通过创建对象模型以及(可选)为问题域建模的领域特定语言,可以设置自己编写与自然语言非常接近的规则。他们以自己的语言表达自己的逻辑,这可能是非技术领域的专家可以理解的,并且所有程序都经过检查,而技术知识则隐藏在常规代码中。

为什么调研drools?
  1. 因为drools是比较成熟的规则引擎,且社区活跃,且实现形式多样化。

  2. 和Java的数据交换

    1. 在Drools的规则中,你可以通过import关键字来引入java的一些类包类进行调用
    2. Drools中,可以直接引用到fact对象
  3. API以及集成

    1. 需要去定义KieContainer,KBase,KSession一系列对象
    2. 支持了编程式接入,但是在springboot中需要自己写很多配置类来去集成
  4. 侵入性耦合比较

    1. 需要在java代码里需要用到规则的地方用KSession对象去匹配规则进行调用。规则和java是分离的。在调用层面耦合了KSession调用对象
  5. 规则表达式

    1. Drools的规则表达式为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现
    2. Drools的规则表达式贴近自然编程语言,拥有自己的扩展名文件drl,语法支持全,基本上自然编程语言有的语法drl全有。所以,完全可以把java的逻辑写在drl文件中
    3. Drools强调逻辑的片段规则化,你可以把核心易变部分写成一个规则文件,等同于原先写在java里的代码现在搬迁到了规则文件。规则文件里的代码全都是可以热变更的
非要不可?

如果不用规则引擎,所有的判定写在代码中比较繁琐且不宜解耦,所以引用规则引擎。

不用会有什么影响?

可扩展性不高,需求规则变动就要改动代码逻辑。

为什么判定引用drools可以剥离业务?

drools有DRL语法可以处理规则,不需要在业务代码中实现,业务代码中只需要引用规则,接收处理规则后的结果即可。

drools是什么?

Drools 是一个开源的规则引擎,用于实现业务规则的管理和执行。它提供了一个声明式的规则语言,允许开发人员将业务规则从应用程序代码中分离出来,并以可读、可维护的方式进行定义和管理。从而提高系统的可扩展性和灵活性。

做什么工作?
  1. 规则管理:将业务规则集中存储在规则文件(如 DRL 文件)中,并提供工具和结构化语法来定义规则。规则可以根据业务需求进行添加、修改和删除。
  2. 规则评估:通过规则引擎对事实数据和上下文进行评估,根据规则条件判断是否满足规则,并执行相应的操作。规则引擎支持灵活的条件和动作,可以根据事实数据和规则逻辑进行推理和决策。
  3. 规则执行:在运行时,规则引擎会加载规则文件并创建一个规则会话(KieSession),然后将事实数据插入到会话中。规则引擎会自动匹配符合条件的规则,并执行相关的动作。规则执行可以是单向的,也可以是循环的,直到满足终止条件。
  4. 动态规则:Drools 可以支持动态加载和更新规则,这意味着可以在应用程序运行时添加、修改和删除规则,而无需重新部署整个应用程序。这种能力对于需要频繁更改业务规则的场景非常有用。
怎么用?
<properties>
       <drools.version>7.61.0.Final</drools.version>
   </properties>

   <!--kie服务包-->
           <dependency>
               <groupId>org.kie</groupId>
               <artifactId>kie-spring</artifactId>
               <version>${drools.version}</version>
           </dependency>
           <dependency>
               <groupId>org.kie.server</groupId>
               <artifactId>kie-server-client</artifactId>
               <version>${drools.version}</version>
           </dependency>
           <dependency>
               <groupId>org.kie.server</groupId>
               <artifactId>kie-server-api</artifactId>
               <version>${drools.version}</version>
           </dependency>
   <!-- drools依赖 -->
           <dependency>
               <groupId>org.drools</groupId>
               <artifactId>drools-templates</artifactId>
               <version>${drools.version}</version>
           </dependency>
方案一:

项目resources下创建好所需规则,通过规则匹配,可指定规则。

package cn.flowboot.config;

import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

/**
* @author gr
* @description: 读取resources下所有drl规则文件
* @date 2023/8/1 17:04
*/
@Configuration
public class KiaSessionConfig {
   private static final String RULES_PATH = "rules/";

   @Bean
   public KieFileSystem kieFileSystem() throws IOException {
       KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
       for (Resource file : getRuleFiles()) {
           kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
      }
       return kieFileSystem;
  }

   private Resource[] getRuleFiles() throws IOException {

       ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
       final Resource[] resources = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
       return resources;

  }

   @Bean
   public KieContainer kieContainer() throws IOException {

       final KieRepository kieRepository = getKieServices().getRepository();
       kieRepository.addKieModule(new KieModule() {
           public ReleaseId getReleaseId() {
               return kieRepository.getDefaultReleaseId();
          }
      });

       KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
       kieBuilder.buildAll();
       return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());

  }

   private KieServices getKieServices() {
       return KieServices.Factory.get();
  }

   @Bean
   public KieBase kieBase() throws IOException {
       return kieContainer().getKieBase();
  }

   @Bean
   public KieSession kieSession() throws IOException {
       return kieContainer().newKieSession();
  }
}

rule.drl

package rules
import cn.flowboot.entity.*
import java.lang.Integer
dialect  "java"


rule "Eligibility Rule//规则名称,必需,且需唯一
salience 10 //数值越大越优先执行
lock-on-active true // 适合计算规则,其中许多修改事实的规则并且您不希望任何规则重新匹配和再次触发。
no-loop true //当`当前规则规则的结果`修改了`对象时,是否可以再次执行该规则   可能会导致死循环。指的是当前规则的修改,如果别的规则修改了,还会导致该规则的触发
when
   $personPerson(age >= 18, income != null)
   $incomeIncome(amount >= 5000)
   not LoanApplication(person == $person)
then
 System.out.println($person.getName() + " is eligible for a loan.");
end


rule "Approval Rule"
salience $dynamicSalience //动态优先级
auto-focus true
duration 10 //延迟执行
when
   $dynamicSalienceInteger()
 $applicationLoanApplication($person: person, amount <= $person.getIncome().getAmount())
then
 //drools.halt(); //终止后续规则
 System.out.println("Loan application approved for " + $person.getName() + " with amount " + $application.getAmount());
end

rule "Rejection Rule"
salience 10
when
 $applicationLoanApplication($person: person, amount > $person.getIncome().getAmount() * 0.3)
then
 System.out.println("Loan application rejected for " + $person.getName() + " with amount " + $application.getAmount());
end
@SpringBootTest
@RunWith(SpringRunner.class)
class DroolsApplicationTests {
   @Autowired
   private KieSession session;
   
   @Test
   public void loanTest(){
       Person person = new Person();
       person.setName("allen");
       person.setAge(25);
       Income income = new Income(6000);
       person.setIncome(income);
       LoanApplication loanApplication = new LoanApplication(person,3000);
       session.insert(person);
       session.insert(income);
       session.insert(loanApplication);

       Person mary = new Person();
       mary.setName("Mary");
       mary.setAge(30);
       Income maryIncome = new Income(8000);
       mary.setIncome(maryIncome);
       LoanApplication maryApplication = new LoanApplication(mary, 10000);
       session.insert(mary);
       session.insert(maryIncome);
       session.insert(maryApplication);

       Person george = new Person();
       george.setName("george");
       george.setAge(28);
       Income georgeIncome = new Income(5000);
       george.setIncome(georgeIncome);
       session.insert(george);
       session.insert(georgeIncome);
       //向工作内存中插入一个Integer值 用于动态获取优先级
       session.insert(11);
       // 读取前缀为Approval_的规则
       //session.fireAllRules(new RuleNameStartsWithAgendaFilter("Approval_"));
       session.fireAllRules();
  }
   
   @AfterEach
   public void runDispose() {
       session.dispose();//释放资源
  }
   
}
方案二:

通过下面的指令来下载“drools-workbench-showcase”

docker pull docker.io/jboss/drools-workbench-showcase

通过下面的指令来将“drools-workbench-showcase”加载到容器中。

docker run -p 8080:8080 -p 8001:8001 -d --name drools-workbench-showcase  docker.io/jboss/drools-workbench-showcase:latest

其“drools-workbench-showcase”的访问地址为:

http://localhost:8080/drools-wb

账号密码为:admin

示例:(非本人博客,仅供参考) blog.csdn.net/Vincent_Vic…

测试示例:


@SpringBootTest
@RunWith(SpringRunner.class)
class DroolsApplicationTests {

   public static final String SERVER_URL = "http://localhost:8180/kie-server/services/rest/server";
   public static final String PASSWORD = "admin";
   public static final String USERNAME = "admin";
   public static final String KIE_CONTAINER_ID = "order-rules_1.0.0-SNAPSHOT";

   @Test
   public void workBenchTest(){
// KisService 配置信息设置
       KieServicesConfiguration kieServicesConfiguration =
               KieServicesFactory.newRestConfiguration(SERVER_URL, USERNAME, PASSWORD, 10000L);
       kieServicesConfiguration.setMarshallingFormat(MarshallingFormat.JSON);

       // 创建规则服务客户端
       KieServicesClient kieServicesClient = KieServicesFactory.newKieServicesClient(kieServicesConfiguration);
       RuleServicesClient ruleServicesClient = kieServicesClient.getServicesClient(RuleServicesClient.class);

       // 规则输入条件
       Order order = new Order();
       order.setAmount(500.0);


       // 命令定义,包含插入数据,执行规则
       KieCommands kieCommands = KieServices.Factory.get().getCommands();
       List<Command<?>> commands = new LinkedList<Command<?>>();
       commands.add(kieCommands.newInsert(order, "order"));
       commands.add(kieCommands.newFireAllRules());
       ServiceResponse<ExecutionResults> results = ruleServicesClient.executeCommandsWithResults(KIE_CONTAINER_ID,
               kieCommands.newBatchExecution(commands,"kiesession1"));

       // 返回值读取
       Order value = (Order) results.getResult().getValue("order");
       System.out.println(JSONObject.toJSONString(value));
  }
}
方案三:

规则配置到数据库,通过数据库可以变更规则,通过调用reload重新加载即可。

package com.sensetime.irdc.sensedunk.business.rule.config;

import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

/**
* @author gr
* @description:
* @date 2023/8/2 10:47
*/
@Configuration
public class KiaSessionConfig {
   private static final String RULES_PATH = "rules/";

   @Bean
   @ConditionalOnMissingBean(KieFileSystem.class)
   public KieFileSystem kieFileSystem() throws IOException {
       KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
       for (Resource file : getRuleFiles()) {
           kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
      }
       return kieFileSystem;
  }

   private Resource[] getRuleFiles() throws IOException {

       ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
       final Resource[] resources = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
       return resources;

  }

   @Bean
   @ConditionalOnMissingBean(KieContainer.class)
   public KieContainer kieContainer() throws IOException {

       final KieRepository kieRepository = getKieServices().getRepository();
       kieRepository.addKieModule(new KieModule() {
           public ReleaseId getReleaseId() {
               return kieRepository.getDefaultReleaseId();
          }
      });

       KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
       kieBuilder.buildAll();
       return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());

  }

   private KieServices getKieServices() {
       return KieServices.Factory.get();
  }

   @Bean
   @ConditionalOnMissingBean(KieBase.class)
   public KieBase kieBase() throws IOException {
       return kieContainer().getKieBase();
  }

   @Bean
   @ConditionalOnMissingBean(KieSession.class)
   public KieSession kieSession() throws IOException {
       return kieContainer().newKieSession();
  }

   @Bean
   @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
   public KModuleBeanFactoryPostProcessor kiePostProcessor(){
       return new KModuleBeanFactoryPostProcessor();
  }
}

规则写入表中

package rules
import com.sensetime.irdc.sensedunk.common.entity.rdb.rule.*
import com.sensetime.irdc.sensedunk.business.rule.repository.RuleDrlRepository
import com.sensetime.irdc.sensedunk.business.rule.repository.impl.RuleDrlRepositoryImpl
global org.slf4j.Logger logger
dialect  ""java""

rule ""service_01""
   lock-on-active true
   no-loop true
   when
       $p PeopleEntity()
   then
       RuleDrlRepository ruleRepository = new RuleDrlRepositoryImpl();
       $p.setName(""213"");
       logger.info(""people:""+$p);
       ruleRepository.test($p);
end

通过KieHelper启动加载

package com.sensetime.irdc.sensedunk.business.rule.repository.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sensetime.irdc.sensedunk.business.rule.mapper.RuleDrlMapper;
import com.sensetime.irdc.sensedunk.business.rule.repository.RuleDrlRepository;
import com.sensetime.irdc.sensedunk.common.entity.rdb.rule.PeopleEntity;
import com.sensetime.irdc.sensedunk.common.entity.rdb.rule.RuleDrlEntity;
import org.kie.api.builder.*;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.utils.KieHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.List;

/**
* @author gr
* @description:
* @date 2023/8/2 10:57
*/
@Service
public class RuleDrlRepositoryImpl implements RuleDrlRepository {

   @Autowired
   private RuleDrlMapper ruleDrlMapper;

   @Autowired
   public static KieContainer kieContainer;

   @PostConstruct
   public void init(){
       reload();
  }

   @Override
   public List<RuleDrlEntityruleDrlList() {
       LambdaQueryWrapper<RuleDrlEntity> queryWrapper = new LambdaQueryWrapper<>();
       return ruleDrlMapper.selectList(queryWrapper);
  }

   @Override
   public void reload() {
       //加载最新规则
       KieHelper kieHelper = new KieHelper();
       List<RuleDrlEntity> ruleDrlEntityList = ruleDrlList();
       for (RuleDrlEntity rule : ruleDrlEntityList){
           String drl = rule.getRuleContent();
           kieHelper.addContent(drl, ResourceType.DRL);
      }
       Results results = kieHelper.verify();
       if (results.hasMessages(Message.Level.ERROR)){
           System.out.println(results.getMessages());
           throw new IllegalArgumentException("error");
      }
       kieContainer = kieHelper.getKieContainer();
  }

   @Override
   public void test(PeopleEntity people){
       System.out.println(people);
  }
}

测试示例 输出:

package com.sensetime.irdc.sensedunk.business;

import com.sensetime.irdc.sensedunk.business.rule.repository.RuleDrlRepository;
import com.sensetime.irdc.sensedunk.business.rule.repository.impl.RuleDrlRepositoryImpl;
import com.sensetime.irdc.sensedunk.common.entity.rdb.rule.PeopleEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @author gr
* @description:
* @date 2023/8/2 10:58
*/
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class DroolsDBTests {

   @Autowired
   private RuleDrlRepositoryImpl ruleDrlRepositoryImpl;

   @Autowired
   private RuleDrlRepository ruleDrlRepository;

   @Test
   public void ruleDroolsTests(){
       
       KieSession kieSession = ruleDrlRepositoryImpl.kieContainer.newKieSession();
       try {
           PeopleEntity people = new PeopleEntity();
           people.setAge(12);

           kieSession.insert(people);
           //加入全局变量log
           kieSession.setGlobal("logger",log);
           kieSession.fireAllRules();
      }finally {
           kieSession.dispose();
      }
  }

   public void reload(){
       //加载最新规则
       ruleDrlRepository.reload();
  }
}
输出结果:
   PeopleEntity(sex=0, name=213, age=12, drlType=null)
   people:PeopleEntity(sex=0, name=213, age=12, drlType=null)
什么时候引用?

Drools规则引擎通常在以下情况下使用:

  1. 业务规则复杂:当业务规则复杂,且需要实时更新或扩展时,Drools可以帮助开发人员快速高效地管理规则。
  2. 决策流程复杂:当决策流程复杂,且需要支持实时决策时,Drools可以帮助开发人员以灵活的方式执行决策流程。
  3. 需要处理大量数据:当需要处理大量数据,并对其进行实时分析和决策时,Drools可以帮助开发人员以高效的方式处理数据。
  4. 需要动态决策:当需要在应用程序中动态地更改决策时,Drools可以帮助开发人员实现这一目标。

总的来说,如果您需要解决一个复杂的业务问题,需要动态地处理决策流程,或者需要处理大量数据,则Drools可能是一个不错的选择。然而,最终的决策应该根据具体需求和环境来做出。

维护成本:

Drools的规则学习成本挺高的。由于是自研的规则语法,需要一个很全面的熟悉过程。而且文档全英文。