在許多業(yè)務(wù)系統(tǒng)中,會(huì)有大量的業(yè)務(wù)規(guī)則配置,而且隨著政策制度、業(yè)務(wù)流程甚至是管理者的決策發(fā)生變化,這些業(yè)務(wù)規(guī)則也需要進(jìn)行更改。這種變化在一些行業(yè)特別頻繁,并且要求快速響應(yīng)。
規(guī)則引擎的作用是為了適應(yīng)這種變更需求,實(shí)現(xiàn)業(yè)務(wù)系統(tǒng)快速且低成本的更新。一般是將業(yè)務(wù)規(guī)則的配置單獨(dú)拿出來(lái),使之與業(yè)務(wù)系統(tǒng)保持低耦合,如果這個(gè)用于配置的模塊做得足夠通用且獨(dú)立,那么它就可以成為一個(gè)規(guī)則引擎系統(tǒng)。通過(guò)規(guī)則引擎可以快速響應(yīng)業(yè)務(wù)規(guī)則的變化。這種方式不需要修改代碼,減少了修改業(yè)務(wù)代碼之后出現(xiàn)錯(cuò)誤的可能性,如果規(guī)則引擎提供前端操作界面,還能夠支持業(yè)務(wù)人員輕松上手配置業(yè)務(wù)規(guī)則。
本文主要分享一些基于Java的規(guī)則引擎,這些規(guī)則引擎是目前比較流行的項(xiàng)目,包括:Drolls、Easy RulesRuleBook、OpenL Tablets。并簡(jiǎn)單介紹這些規(guī)則引擎的使用方式。
https://www.drools.org/
https://github.com/kiegroup/drools
Drools是一個(gè)業(yè)務(wù)規(guī)則管理系統(tǒng)(BRMS)。主要功能模塊包括:核心業(yè)務(wù)規(guī)則引擎(BRE)、Web創(chuàng)作和規(guī)則管理應(yīng)用程序(Drools Workbench)、決策模型和符號(hào)(DMN)模型以及用于開(kāi)發(fā)的IDE插件(idea、eclipse等)。
Drools體系架構(gòu)如下圖所示:
Drools架構(gòu)的執(zhí)行步驟如下:
以下是SpringBoot的Drools使用例子。
創(chuàng)建一個(gè)基本的springBoot應(yīng)用程序,并將drools依賴項(xiàng)添加到pom.xml。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.praveen.drools.example</groupId> <artifactId>springboot-drools-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-drools-demo</name> <description>Demo project for Spring Boot with Drools Engine</description> <properties> <java.version>11</java.version> <drools.version>7.67.0.Final</drools.version> <springfox-swagger2.version>3.0.0</springfox-swagger2.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>${drools.version}</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools.version}</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-decisiontables</artifactId> <version>${drools.version}</version> </dependency> <!-- swagger ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> <version>${springfox-swagger2.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${springfox-swagger2.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
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 */@Configurationpublic 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()); }}
這個(gè)配置類(lèi)創(chuàng)建一個(gè)springbean KieContainer,通過(guò)加載應(yīng)用程序/resources文件夾下的規(guī)則文件來(lái)構(gòu)建規(guī)則引擎。
創(chuàng)建KieFileSystem實(shí)例并從應(yīng)用程序的resources目錄加載DRL文件。接著使用KieService和KieBuilder創(chuàng)建KieContainer并將其配置為spring bean。
創(chuàng)建名為CustomerRequest的Pojo類(lèi)和字段。
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(); }}
我們將這個(gè)類(lèi)作為請(qǐng)求對(duì)象參數(shù)傳給規(guī)則引擎,并且將字段作為輸入發(fā)送到定義的規(guī)則中,以便為派生customerType。
另外,再定義了一個(gè)名為CustomerCategory.java的java枚舉,用于保存客戶類(lèi)別,規(guī)則引擎根據(jù)該值派生客戶類(lèi)型。
package com.praveen.drools.example.model;/** * Customer Categories. */public enum CustomerCategory { GENERAL, KIDS, SENIOR_CITIZEN, SUSPENDED; public String getValue() { return this.toString(); }}
創(chuàng)建一個(gè)名為CustomerType的響應(yīng)POJO類(lèi),如下所示。
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(); }}
創(chuàng)建一個(gè)名為customer-category.drl的drools規(guī)則文件,并將該文件放在目錄/src/main/resources/rules下。
import com.praveen.drools.example.model.CustomerRequestimport 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 < 20) then customerType.setCustomerType(CustomerCategory.KIDS);endrule "Categorize senior citizen customer based on age" when CustomerRequest(age > 50) then customerType.setCustomerType(CustomerCategory.SENIOR_CITIZEN);endrule "Categorize customer based on number of orders" when CustomerRequest(numberOfOrders == 0) then customerType.setCustomerType(CustomerCategory.SUSPENDED);endrule "Categorize customer general case" when CustomerRequest((gender == "M" || gender == "F") && age > 20 && age < 50) then customerType.setCustomerType(CustomerCategory.GENERAL);end
需要在DRL文件中import 使用到的模型。定義一個(gè)名為customerType的全局參數(shù),作為多個(gè)規(guī)則之間共享數(shù)據(jù)。
DRL文件可以包含一個(gè)或多個(gè)規(guī)則。可以使用mvel語(yǔ)法來(lái)指定規(guī)則。此外,每個(gè)規(guī)則都可以使用rule關(guān)鍵字進(jìn)行描述。
然后定義when-then語(yǔ)法來(lái)指定規(guī)則的條件。根據(jù)Customer請(qǐng)求的輸入值,我們將設(shè)置customerType結(jié)果。
創(chuàng)建一個(gè)名為CustomerCategorizeService的服務(wù)類(lèi),并添加以下內(nèi)容。
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實(shí)例創(chuàng)建KieSession實(shí)例。返回一個(gè)CustomerType類(lèi)型的全局參數(shù),這個(gè)CustomerType將用于保存規(guī)則執(zhí)行結(jié)果。
使用insert方法將customerRequest傳遞給DRL文件,然后我們通過(guò)調(diào)用fireAllRules方法觸發(fā)所有規(guī)則,最后通過(guò)調(diào)用KieSession的dispose方法終止會(huì)話。
接著開(kāi)發(fā)一個(gè)Controller 將服務(wù)發(fā)布為一個(gè)API: /API/getCustomerType。API的入?yún)镃ustomerRequest對(duì)象,返回類(lèi)型為CustomerType。Controller代碼如下所示:
packagecom.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<CustomerType> getCustomer(@RequestBody CustomerRequest customerRequest) { CustomerType customerType = customerCategorizeService.getCustomerType(customerRequest); return new ResponseEntity<>(customerType, HttpStatus.OK); }}
https://github.com/j-easy/easy-rules
EasyRule是一個(gè)輕量級(jí)的規(guī)則引擎。它提供了用于創(chuàng)建規(guī)則的抽象以及規(guī)則引擎API,它通過(guò)運(yùn)行一組規(guī)則來(lái)檢測(cè)條件并執(zhí)行操作。
以下是EasyRule的一些核心特性:
以下是Java中使用EasyRules的例子:
<dependency> <groupId>org.jeasy</groupId> <artifactId>easy-rules-core</artifactId> <version>3.3.0</version></dependency>
Easy Rules提供了一些選項(xiàng)來(lái)創(chuàng)建規(guī)則:
定義方式如下面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); }}
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); }}
https://github.com/deliveredtechnologies/rulebook
RuleBook提供了一個(gè)簡(jiǎn)單、靈活并且直觀的DSL。RuleBook提供了易于使用的Lambda特定語(yǔ)言或POJO方式來(lái)定義規(guī)則,Java開(kāi)發(fā)人員可以通過(guò)帶注解的POJO來(lái)組織大規(guī)模規(guī)則集合,替代那些又臭又長(zhǎng)的“if/else”。
以下是在Java使用RuleBook的Demo。
<dependency> <groupId>com.deliveredtechnologies</groupId> <artifactId>rulebook-core</artifactId> <version>${version}</version></dependency>
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());}
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); }}
http://openl-tablets.org/
https://github.com/openl-tablets/openl-tablets
OpenL Tablets 是一個(gè)基于 Java和Excel決策表工具的業(yè)務(wù)規(guī)則引擎(BRE)和業(yè)務(wù)規(guī)則管理系統(tǒng)(BRMS)。
主要包括以下幾個(gè)部分:
以下是在Java中使用OpenL Tablets的例子。
<dependency> <groupId>org.openl</groupId> <artifactId>org.openl.core</artifactId> <version>${version}</version></dependency><dependency> <groupId>org.openl.rules</groupId> <artifactId>org.openl.rules</artifactId> <version>${version}</version></dependency>
(2)java實(shí)現(xiàn)
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()); }}
本文鏈接:http://www.tebozhan.com/showinfo-26-13643-0.html四個(gè)流行的Java開(kāi)源規(guī)則引擎和入門(mén)
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com