规则引擎实战

2022/11/02 Drools Rules

Drools 规则引擎实战

Drools常见的使用场景

Drools是一个规则引擎,通常用于实现业务规则管理和自动决策。下面是Drools常见的使用场景:

  • 客户端决策:Drools可以被用来处理客户端决策问题,如客户经理决策、客户评分卡、授信决策、风险评估等。
  • 规则管理:Drools可以作为一个独立的规则管理平台,帮助企业管理规则和流程。
  • 基于规则的业务流程管理:Drools可以集成到业务流程管理系统中,帮助企业管理规则驱动的业务流程。
  • 数据验证和清洗:Drools可以被用来对数据进行验证和清洗,以确保数据的准确性和一致性。
  • 基于事件的决策:Drools可以被用来实现基于事件的决策,如基于实时事件的促销决策、实时交易监测等。

数据验证和清洗

Drools可以在数据验证和清洗场景中使用,通过使用规则来验证数据的有效性和一致性。具体来说,Drools可以用于:

  • 数据格式验证:可以使用Drools验证数据是否符合特定的格式要求,如邮件地址、电话号码等。
  • 数据范围验证:可以使用Drools验证数据是否在允许的范围内,如年龄范围、工资范围等。
  • 数据一致性验证:可以使用Drools验证不同数据项之间的一致性,如邮寄地址和电话号码的一致性等。
  • 数据清洗:可以使用Drools对数据进行清洗,如删除重复的数据、修正错误的数据等。

通过使用Drools进行数据验证和清洗,可以保证数据的准确性和一致性,从而提高数据的可靠性和可用性。

如下几个规则是关于Drools在数据清洗场景中的示例

删除重复的数据

rule "Remove duplicate records"
when
    $record1: Record( $id1: id, $name1: name, $age1: age, $email1: email )
    $record2: Record( id == $id1, name == $name1, age == $age1, email == $email1 )
    $record1 != $record2
then
    retract( $record2 );
end

修正错误的数据

rule "Correct invalid email addresses"
when
    $record: Record( email not matches "(\\w+\\.)*\\w+@\\w+\\.\\w+" )
then
    modify( $record ) { setEmail( "invalid" ) };
end

数据格式转换 使用Drools对数据进行格式转换,如将日期字段从 一种格式转换为另一种格式等。

package com.example.drools.rules;

import com.example.drools.DataCleaningExample.Record;

rule "Remove Records with Invalid Age"
when
    $record : Record(age < 0 || age > 120)
then
    retract($record);
end

上面的规则演示了标准化地址的功能(将所有地址字段转换为大写字母)。

去除噪声数据 使用Drools对数据进行预处理,如去除数据中的噪声等。

package com.example.drools.rules;

import com.example.drools.DataCleaningExample.Record;

rule "Remove Outliers - Age"
when
    $record : Record(age < 18 || age > 65)
then
    retract($record);
end

rule "Remove Outliers - Address"
when
    $record : Record(address matches ".*[0-9].*")
then
    retract($record);
end

这是两个Drools规则,分别演示了如何删除年龄的离群值(年龄小于18或大于65)和地址的离群值(地址中包含数字)。

填补缺失的数据 Drools对数据进行预处理,如填补缺失的数据等。

package com.example.drools.rules;

import com.example.drools.DataCleaningExample.Record;

rule "Fill Missing Name"
when
    $record : Record(name == null || name.isEmpty())
then
    $record.setName("N/A");
end

rule "Fill Missing Age"
when
    $record : Record(age == 0)
then
    $record.setAge(30);
end

rule "Fill Missing Address"
when
    $record : Record(address == null || address.isEmpty())
then
    $record.setAddress("N/A");
end

这是三个Drools规则,分别演示了如何填补缺失的名称,年龄和地址。

客户评分卡

Drools可以用于客户评分卡场景,以评估客户的信用评分。它可以通过评估客户的财务历史、信用历史和个人信息等因素来评分客户。

package com.example.drools.rules;

import com.example.drools.CreditScoringExample.Customer;

rule "Good Credit History"
when
$customer : Customer(creditHistory == "Good")
then
$customer.setScore($customer.getScore() + 20);
end

rule "High Income"
when
$customer : Customer(income > 75000)
then
$customer.setScore($customer.getScore() + 10);
end

rule "Long Employment"
when
$customer : Customer(employmentLength > 5)
then
$customer.setScore($customer.getScore() + 15);
end

基于规则的业务流程管理

Drools可以用于帮助企业管理规则和流程。它可以作为一种途径,帮助企业管理和自动化各种业务规则,如:

  • 权限管理:判断用户是否具有某种特定的权限,以访问特定的功能或数据。
  • 工作流管理:帮助企业管理复杂的业务流程,如请求审批或订单处理。
  • 报价管理:帮助企业根据特定的规则,计算客户的报价。
  • 优惠券管理:帮助企业判断特定的优惠券是否适用于特定的客户。

如下是一个简单的Drools规则示例,演示了如何管理报价:

package com.example.drools.rules;

import com.example.drools.PricingManagementExample.Order;

rule "Discount for High Volume Orders"
when
    $order : Order(quantity > 100)
then
    $order.setDiscount(0.10);
end

rule "Discount for Repeat Customers"
when
    $order : Order(customer.numberOfOrders > 10)
then
    $order.setDiscount(0.05);
end

rule "Discount for Large Orders"
when
    $order : Order(total > 1000)
then
    $order.setDiscount(0.15);
end

这是三个Drools规则,分别演示了如何为大量订单、重复顾客和大订单提供折扣。

基于事件的决策

Drools可以通过使用事件驱动的决策来实现基于事件的决策。它通过监听事件并触发相应的规则来实现事件驱动的决策。规则可以根据事件中包含的信息执行特定的操作,如:

  • 审批请求:当客户发起请求时,根据请求类型和客户信息执行相应的审批操作。
  • 发送通知:当特定事件发生时,发送通知到相关的人员或系统。
  • 执行操作:当特定事件发生时,执行特定的操作,如修改数据或发送请求。

如下是一个简单的Drools规则示例,演示了如何执行基于事件的决策:

package com.example.drools.rules;

import com.example.drools.EventDrivenDecisionMakingExample.OrderEvent;

rule "Approve High Volume Orders"
when
    $orderEvent : OrderEvent(order.quantity > 100)
then
    $orderEvent.getOrder().setApproved(true);
end

rule "Send Notification for Large Orders"
when
    $orderEvent : OrderEvent(order.total > 1000)
then
    // Send notification to relevant personnel or system
end

rule "Update Inventory for Approved Orders"
when
    $orderEvent : OrderEvent(order.isApproved() == true)
then
    // Update inventory information
end

这是三个Drools规则,分别演示了如何批准大量订单、发送通知和更新库存信息。

银行业务审批

在银行业务审批中,Drools可以应用于贷款审批和信用卡审批等场景。通过规则引擎,银行可以根据客户的贷款申请和信用历史等信息,自动化地判断是否批准贷款或信用卡。

下面是一个简单的银行业务审批案例,演示了Drools如何在银行业务审批中使用:

规则文件(loan-approval.drl)

package rules;

import org.dtt.entity.*;

rule "Reject Loan for Applicant with Bad Credit Score"
when
  $applicant:Applicant(creditScore < 600)
  $loanApplication:LoanApplication(applicant == $applicant)
then
  $loanApplication.setApproved(false);
  $loanApplication.setReason("Rejected due to low credit score");
  System.out.println("Rejected due to low credit score");
  retract($loanApplication);
end

rule "Approve Loan for Applicant with Good Credit Score"
when
  $applicant:Applicant(creditScore >= 600)
  $loanApplication:LoanApplication(applicant == $applicant)
then
  $loanApplication.setApproved(true);
  $loanApplication.setReason("Approved");
  System.out.println("Approve Loan for Applicant with Good Credit Score --> approved");
end

rule "Reject Loan for Applicant with Low Income"
when
  $applicant:Applicant(income < 5000)
  $loanApplication:LoanApplication(applicant == $applicant)
then
  $loanApplication.setApproved(false);
  $loanApplication.setReason("Rejected due to low income");
  retract($loanApplication);
  System.out.println("Reject Loan for Applicant with Low Income --> reject");
end


rule "print"
when
  $loanApplication: LoanApplication()
then
  System.out.println($loanApplication.getApplicant().getName());
end

Fact类

import java.io.Serializable;
public class Applicant implements Serializable {
    private String name;
    private int age;
    private int creditScore;
    private double income;
    public LoanApplication(Applicant applicant, Integer loanAmount) {
        this.applicant = applicant;
        this.loanAmount = loanAmount;
    }
//省略Getters/Setters方法
}
public class LoanApplication {
   private Applicant applicant;
    private Integer loanAmount;
    private boolean approved;
    private String reason;
	//省略Getters/Setters方法
}

测试示例 在我们运行TestLoanApproval代码时,它会触发Drools规则引擎,并使用LoanApplication和Applicant对象作为规则的输入。Drools规则引擎将按照定义的规则来评估这些对象,并决定是否批准贷款申请。

如果所有的规则都被评估为true,那么贷款申请将被批准。在这种情况下,LoanApprovalTest中的断言语句将不会抛出任何异常,代表测试成功。

如果任何一个规则评估为false,那么贷款申请将不会被批准。在这种情况下,LoanApprovalTest中的断言语句将抛出异常,代表测试失败。

 @Test
public void testLoanApproval() {
    KieServices kieServices = KieServices.Factory.get();
    KieContainer kieContainer = kieServices.newKieClasspathContainer();
    KieSession kieSession = kieContainer.newKieSession();
    Applicant applicant = new Applicant("Joe", 23, 900, 10000);
    LoanApplication loanApplication = new LoanApplication(applicant);
    loanApplication.setLoanAmount(100000);
    kieSession.insert(applicant);
    kieSession.insert(loanApplication);
    kieSession.fireAllRules();
    kieSession.dispose();
    assertTrue(loanApplication.isApproved());
}

小明喝汽水问题

1元钱一瓶汽水,喝完后两个空瓶换一瓶汽水,问:小明有20元钱,最多可以喝到几瓶汽水?

规则拆分 规则1:1元钱一瓶汽水–> 有钱就买水,空瓶+1,钱-1,喝水+1 规则2:两个空瓶换一瓶汽水–>有空瓶就换钱,空瓶-2,钱+1

//规则1:1元钱一瓶汽水。有钱就买水,空瓶+1,钱-1,喝水+1;
        rule "rule1"
            salience 3
            when
                $m:XiaoMing(money>0);
            then
                System.out.println("有钱即可喝水,钱:"+$m.getMoney());
                $m.setBottle($m.getBottle()+1);
                $m.setMoney($m.getMoney()-1);
                $m.setDrink($m.getDrink()+1);
                update($m)
        end

需要注意的是,如果想要换水,需要再编写一个规则,描述空瓶的变化情况。

该规则描述了:当XiaoMing实例的bottle属性大于等于2时,空瓶减少2瓶,喝水量增加1瓶,并且更新XiaoMing实例。

//规则2:两个空瓶换一瓶汽水。有空瓶就换钱,空瓶-2,钱+1;
        rule "rule2"
            salience 2
            when
                $m:XiaoMing(bottle>=2);
            then
                System.out.println("有瓶子就换钱,瓶子:"+$m.getBottle());
                $m.setBottle($m.getBottle()-2);
                $m.setMoney($m.getMoney()+1);
                update($m)
        end

如果想要在Drools规则中打印喝水数量,可以在合适的地方添加一行代码,例如在最终状态打印喝水数量:

//规则3,当XiaoMing实例的money属性小于等于0时,打印喝水数量
        rule "rule3"
            salience 1
            when
                $m:XiaoMing(money<=0);
            then
                System.out.println("总共喝掉:"+$m.getDrink());
        end

Fact类

/**
 * Fact定义
 */
public class XiaoMing {

    //总共的钱
    private int money;
    //空瓶子数目
    private int bottle;
    //已经喝掉的汽水
    private int drink;
	//省略getters、setters方法.....
}

测试方法

@Test
public void test01() {
        KieContainer kc = KieServices.Factory.get().getKieClasspathContainer();
        System.out.println(kc.verify().getMessages().toString());
        KieSession ksession = kc.newKieSession("mingKS");
        XiaoMing xiaoMing=new XiaoMing();
        xiaoMing.setMoney(20);
        ksession.insert(xiaoMing);
        ksession.fireAllRules();
        ksession.dispose();
    }

高尔夫球员站位问题

问题分析 已知有四个高尔夫球员,他们的名字是Fred,Joe,Bob,Tom;今天他们分别穿着红色,蓝色,橙色,以及格子衣服,并且他们按照从左往右的顺序站成一排。我们将最左边的位置定为1,最右边的位置定为4,中间依次是2,3位置。

现在我们了解的情况是:

  • 高尔夫球员Fred,目前不知道他的位置和衣服颜色
  • Fred右边紧挨着的球员穿蓝色衣服
  • Joe排在第2个位置
  • Bob穿着格子短裤
  • Tom没有排在第1位或第4位,也没有穿橙色衣服 请问,这四名球员的位置和衣服颜色。

规则

package com.xshe.drools

import com.xshe.drools.bean.Golfer;

rule "find solution"
    when
        //1.高尔夫球员Fred,目前不知道他的位置和衣服颜色
        $fred : Golfer( name == "Fred" )

        //3.Joe排在第2个位置
        $joe : Golfer( name == "Joe",
                position == 2,
                position != $fred.position,
                color != $fred.color )

        //4.Bob穿着格子短裤
        $bob : Golfer( name == "Bob",
                position != $fred.position,
                position != $joe.position,
                color == "plaid",
                color != $fred.color,
                color != $joe.color )

        //5.Tom没有排在第1位或第4位,也没有穿橙色衣服
        $tom : Golfer( name == "Tom",
                position != 1,
                position != 4,
                position != $fred.position,
                position != $joe.position,
                position != $bob.position,
                color != "orange",
                color != $fred.color,
                color != $joe.color,
                color != $bob.color )

        //2.Fred右边紧挨着的球员穿蓝色衣服
        Golfer( position == ( $fred.position + 1 ),
                      color == "blue",
                      this in ( $joe, $bob, $tom ) )

    then
        System.out.println( "Fred " + $fred.getPosition() + " " + $fred.getColor() );
        System.out.println( "Joe " + $joe.getPosition() + " " + $joe.getColor() );
        System.out.println( "Bob " + $bob.getPosition() + " " + $bob.getColor() );
        System.out.println( "Tom " + $tom.getPosition() + " " + $tom.getColor() );
end

测试

public static void main(final String[] args) {
        KieContainer kc = KieServices.Factory.get().getKieClasspathContainer();
        System.out.println(kc.verify().getMessages().toString());
        KieSession ksession = kc.newKieSession("golfKS");
        String[] names = new String[]{"Fred", "Joe", "Bob", "Tom"};
        String[] colors = new String[]{"red", "blue", "plaid", "orange"};
        int[] positions = new int[]{1, 2, 3, 4};

        for (String name : names) {
            for (String color : colors) {
                for (int position : positions) {
                    ksession.insert(new Golfer(name, color, position));
                }
            }
        }
        ksession.fireAllRules();
        ksession.dispose();
    }

SpringBoot规则引擎来实现打折

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

引入依赖

我们创建一个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>  

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

添加业务Model

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

@Getter  
@Setter  
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 ,用来表示计算得到的最终的折扣,如下所示。

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

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

定义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  

这个drl文件虽然不是java文件,但还是很容易看懂的。

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

添加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.dispose();  
        return orderDiscount;  
    }  
}  
  • 注入KieContainer实例并创建一个KieSession实例。
  • 设置了一个OrderDiscount类型的全局参数,它将保存规则执行结果。
  • 使用insert()方法将请求对象传递给 drl 文件。
  • 调用fireAllRules()方法触发所有规则。
  • 最后通过调用KieSession 的dispose()方法终止会话。

Search

    微信好友

    博士的沙漏

    Table of Contents