在许多业务系统中,会有大量的业务规则配置,而且随着政策制度、业务流程甚至是管理者的决策发生变化,这些业务规则也需要进行更改。这种变化在一些行业特别频繁,并且要求快速响应。
规则引擎的作用是为了适应这种变更需求,实现业务系统快速且低成本的更新。一般是将业务规则的配置单独拿出来,使之与业务系统保持低耦合,如果这个用于配置的模块做得足够通用且独立,那么它就可以成为一个规则引擎系统。通过规则引擎可以快速响应业务规则的变化。这种方式不需要修改代码,减少了修改业务代码之后出现错误的可能性,如果规则引擎提供前端操作界面,还能够支持业务人员轻松上手配置业务规则。
本文主要分享一些基于Java的规则引擎,这些规则引擎是目前比较流行的项目,包括:Drolls、Easy RulesRuleBook、OpenL Tablets。并简单介绍这些规则引擎的使用方式。
1.Drools
https://www.drools.org/
https://github.com/kiegroup/drools
Drools是一个业务规则管理系统(BRMS)。主要功能模块包括:核心业务规则引擎(BRE)、Web创作和规则管理应用程序(Drools Workbench)、决策模型和符号(DMN)模型以及用于开发的IDE插件(idea、eclipse等)。
Drools体系架构如下图所示:
Drools架构的执行步骤如下:
- 将规则加载到规则库中,该规则库始终保持可用。
- 事实(Facts)被保存到工作内存(Working Memory)中,它们可以被修改或撤回。
- Pattern Matcher将新的或现有的事实与规则进行匹配, 这个过程称为模式匹配,该过程由规则引擎执行。
- agenda在冲突解决策略的帮助下管理冲突规则的执行顺序。
以下是SpringBoot的Drools使用例子。
(1)定义Pom.xml
创建一个基本的springBoot应用程序,并将drools依赖项添加到pom.xml。
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.6.6
com.praveen.drools.example
springboot-drools-demo
0.0.1-SNAPSHOT
springboot-drools-demo
Demo project for Spring Boot with Drools Engine
11
7.67.0.Final
3.0.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.drools
drools-core
${drools.version}
org.drools
drools-compiler
${drools.version}
org.drools
drools-decisiontables
${drools.version}
io.springfox
springfox-boot-starter
${springfox-swagger2.version}
io.springfox
springfox-swagger-ui
${springfox-swagger2.version}
org.springframework.boot
spring-boot-maven-plugin
(2)创建一个名为DroolsConfig.java的java配置类。
package com.praveen.drools.example.configuration;
import com.praveen.drools.example.service.CustomerCategorizeService;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Drools Config.
* @author Praveen.Nair
*/
@Configuration
public class DroolsConfig {
private static final String RULES_CUSTOMER_RULES_DRL = "rules/customer-category.drl";
@Bean
public KieContainer kieContainer() {
final KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
return kieServices.newKieContainer(kieModule.getReleaseId());
}
}
这个配置类创建一个springbean KieContainer,通过加载应用程序/resources文件夹下的规则文件来构建规则引擎。
创建KieFileSystem实例并从应用程序的resources目录加载DRL文件。接着使用KieService和KieBuilder创建KieContainer并将其配置为spring bean。
(3)创建模型类
创建名为CustomerRequest的Pojo类和字段。
package com.praveen.drools.example.model;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Customer request POJO.
* @author Praveen.Nair
*/
public final class CustomerRequest {
private final long id;
private final Integer age;
private final String gender;
private final Integer numberOfOrders;
public CustomerRequest(long id, Integer age, String gender, Integer numberOfOrders) {
this.id = id;
this.age = age;
this.gender = gender;
this.numberOfOrders = numberOfOrders;
}
public long getId() {
return id;
}
public Integer getAge() {
return age;
}
public String getGender() {
return gender;
}
public Integer getNumberOfOrders() {
return numberOfOrders;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CustomerRequest that = (CustomerRequest) o;
return id == that.id &&
Objects.equals(age, that.age) &&
Objects.equals(gender, that.gender) &&
Objects.equals(numberOfOrders, that.numberOfOrders);
}
@Override
public int hashCode() {
return Objects.hash(id, age, gender, numberOfOrders);
}
@Override
public String toString() {
return new StringJoiner(", ", CustomerRequest.class.getSimpleName() + "[", "]")
.add("id=" + id)
.add("age=" + age)
.add("gender=" + gender)
.add("numberOfOrders='" + numberOfOrders + "'")
.toString();
}
}
我们将这个类作为请求对象参数传给规则引擎,并且将字段作为输入发送到定义的规则中,以便为派生customerType。
另外,再定义了一个名为CustomerCategory.java的java枚举,用于保存客户类别,规则引擎根据该值派生客户类型。
package com.praveen.drools.example.model;
/**
* Customer Categories.
*/
public enum CustomerCategory {
GENERAL, KIDS, SENIOR_CITIZEN, SUSPENDED;
public String getValue() {
return this.toString();
}
}
创建一个名为CustomerType的响应POJO类,如下所示。
package com.praveen.drools.example.model;
import java.util.Objects;
import java.util.StringJoiner;
/**
* CustomerType Response model.
* @author Praveen.Nair
*/
public class CustomerType {
private CustomerCategory customerType;
public CustomerCategory getCustomerType() {
return customerType;
}
public void setCustomerType(CustomerCategory customerType) {
this.customerType = customerType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CustomerType that = (CustomerType) o;
return customerType == that.customerType;
}
@Override
public int hashCode() {
return Objects.hash(customerType);
}
@Override
public String toString() {
return new StringJoiner(", ", CustomerType.class.getSimpleName() + "[", "]")
.add("customerType=" + customerType)
.toString();
}
}
(4) 定义Drools规则
创建一个名为customer-category.drl的drools规则文件,并将该文件放在目录/src/main/resources/rules下。
import com.praveen.drools.example.model.CustomerRequest
import com.praveen.drools.example.model.CustomerCategory;
global com.praveen.drools.example.model.CustomerType customerType;
dialect "mvel"
rule "Categorize customer based on age"
when
CustomerRequest(age 50)
then
customerType.setCustomerType(CustomerCategory.SENIOR_CITIZEN);
end
rule "Categorize customer based on number of orders"
when
CustomerRequest(numberOfOrders == 0)
then
customerType.setCustomerType(CustomerCategory.SUSPENDED);
end
rule "Categorize customer general case"
when
CustomerRequest((gender == "M" || gender == "F") && age > 20 && age < 50)
then
customerType.setCustomerType(CustomerCategory.GENERAL);
end
需要在DRL文件中import 使用到的模型。定义一个名为customerType的全局参数,作为多个规则之间共享数据。
DRL文件可以包含一个或多个规则。可以使用mvel语法来指定规则。此外,每个规则都可以使用rule关键字进行描述。
然后定义when-then语法来指定规则的条件。根据Customer请求的输入值,我们将设置customerType结果。
(5) 添加服务层和控制层
创建一个名为CustomerCategorizeService的服务类,并添加以下内容。
package com.praveen.drools.example.service;
import com.praveen.drools.example.model.CustomerRequest;
import com.praveen.drools.example.model.CustomerType;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
/**
* Customer Categorization service.
* @author Praveen.Nair
*/
public class CustomerCategorizeService {
private final KieContainer kieContainer;
public CustomerCategorizeService(KieContainer kieContainer) {
this.kieContainer = kieContainer;
}
public CustomerType getCustomerType(CustomerRequest customerRequest) {
CustomerType customerType = new CustomerType();
KieSession kieSession = kieContainer.newKieSession();
kieSession.setGlobal("customerType", customerType);
kieSession.insert(customerRequest);
kieSession.fireAllRules();
kieSession.dispose();
return customerType;
}
}
使用注入的KieContainer实例创建KieSession实例。返回一个CustomerType类型的全局参数,这个CustomerType将用于保存规则执行结果。
使用insert方法将customerRequest传递给DRL文件,然后我们通过调用fireAllRules方法触发所有规则,最后通过调用KieSession的dispose方法终止会话。
接着开发一个Controller 将服务发布为一个API: /API/getCustomerType。API的入参为CustomerRequest对象,返回类型为CustomerType。Controller代码如下所示:
package
com.praveen.drools.example.web;
import com.praveen.drools.example.model.CustomerRequest;
import com.praveen.drools.example.model.CustomerType;
import com.praveen.drools.example.service.CustomerCategorizeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/getCustomerType")
public class CustomerCategorizeController {
private final CustomerCategorizeService customerCategorizeService;
public CustomerCategorizeController(
CustomerCategorizeService customerCategorizeService) {
this.customerCategorizeService = customerCategorizeService;
}
@PostMapping
public ResponseEntity getCustomer(@RequestBody CustomerRequest customerRequest) {
CustomerType customerType = customerCategorizeService.getCustomerType(customerRequest);
return new ResponseEntity(customerType, HttpStatus.OK);
}
}
2.Easy Rules
https://github.com/j-easy/easy-rules
EasyRule是一个轻量级的规则引擎。它提供了用于创建规则的抽象以及规则引擎API,它通过运行一组规则来检测条件并执行操作。
以下是EasyRule的一些核心特性:
- 轻量级类库和容易上手
- 基于POJO的开发与注解的编程模型
- 基于MVEL表达式的编程模型(适用于极简单的规则,一般不推荐)
- 支持根据简单的规则创建组合规则
- 方便且适用于java的抽象的业务模型规则
以下是Java中使用EasyRules的例子:
(1)在Maven中引入依赖包
org.jeasy
easy-rules-core
3.3.0
(2)定义规则
Easy Rules提供了一些选项来创建规则:
- 声明性注解;
- API;
- 表达式语言;
- 规则描述符。
定义方式如下面Java代码:
@Rule(name = "cart total rule", description = "Give 10% off when shopping cart is greater than $200" )
public class CartTotalRule {
@Condition
public boolean cartTotal(@Fact("cart") Cart cart) {
return cart.isGreaterThanTwoHundered;
}
@Action
public void giveDiscount(@Fact("cart") Cart cart) {
cart.setTotalDiscount(200);
}
}
(3)最后基于事实数据执行规则引擎
public class CartTotalRuleTest {
public static void main(String[] args) {
// define facts
Facts facts = new Facts();
facts.put("cart", get_customer_cart);
// define rules
Rule cartTotalRUle = CartTotalRule
Rules rules = new Rules();
rules.register(cartTotalRUle);
// fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
3.RuleBook
https://github.com/deliveredtechnologies/rulebook
RuleBook提供了一个简单、灵活并且直观的DSL。RuleBook提供了易于使用的Lambda特定语言或POJO方式来定义规则,Java开发人员可以通过带注解的POJO来组织大规模规则集合,替代那些又臭又长的“if/else”。
以下是在Java使用RuleBook的Demo。
(1)Maven
com.deliveredtechnologies
rulebook-core
${version}
(2)Java定义规则
public class Cart{
private double cartTotal;
private String cartId;
private Customer user;
private List cartEntries;
//getter and setter
}
public class ShoppingCartRule extends CoRRuleBook {
@Override
public void defineRules() {
//give 10% off when cart total is greater than $200
addRule(RuleBuilder.create().withFactType(Cart.class).withResultType(Double.class)
.when(facts -> facts.getOne().getCartTotal() > 200)
.then((facts, result) -> result.setValue(20))
.stop()
.build());
}
(3)执行规则:
public class CartPromotionRule {
public static void main(String[] args) {
RuleBook cartPromotion = RuleBookBuilder.create(ShoppingCartRule.class).withResultType(Double.class)
.withDefaultResult(0.0)
.build();
NameValueReferableMap facts = new FactMap();
facts.setValue("cart", new Cart(450.0, 123456, customer, entries));
cartPromotion.run(facts);
}
}
4.OpenL Tablets
http://openl-tablets.org/
https://github.com/openl-tablets/openl-tablets
OpenL Tablets 是一个基于 Java和Excel决策表工具的业务规则引擎(BRE)和业务规则管理系统(BRMS)。
主要包括以下几个部分:
- Business Rules Engines(业务规则引擎)
- WebStudio
- Web services(web服务)
- Rule repository(基于JCR的实现的规则库)
以下是在Java中使用OpenL Tablets的例子。
(1)Maven
org.openl
org.openl.core
${version}
org.openl.rules
org.openl.rules
${version}
(2)java实现
public class Main {
private CartPromotionRules instance;
public static void main(String[] args) {
Main rules = new Main();
// setup user and case here
rules.process(aCase);
}
public void process(Case aCase) {
EngineFactory engineFactory = new RulesEngineFactory(
getClass().getClassLoader()
.getResource("rules.xls"), CartPromotionRules.class);
instance = engineFactory.newEngineInstance();
instance.executePromotion(aCase, new Response());
}
}