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()方法终止会话。