SpringBoot + Drools整合並實作為RESTful API
一、前言
因為最近需要分析個人借貸系統,稍微看了一下規則引擎Drools,再加上使用REST Web API的趨勢,決定使用SpringBoot+Drools來構建簡單的WebService範例供個人借貸系統呼叫使用。規則的部分則使用Drools的規則描述語言DRL(Drools Rule Language)與Excel來描述。
二、實作
1.建立maven專案,在pom.xml中加入SpringBoot與Drools相關的依賴項目,為了簡化程式碼與API使用的便利性也加上lombok的依賴。
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mytest</groupId>
<artifactId>springbootdroolstest</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>springbootdroolstest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<drools.version>7.31.0.Final</drools.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath />
</parent>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</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>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie.server</groupId>
<artifactId>kie-server-client</artifactId>
<version>${drools.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
使用Drools的版本是7.31
2.準備DATA Entity,也是Drools的Model
UserData (審核個人資料)
package com.mytest.springbootdroolstest.model;
@Data
public class UserData {
private String pid;
private String name;
private Integer age;
private String gender;
}
Rule1Result (規則引擎判斷後的結果)
package com.mytest.springbootdroolstest.model;
@Data
public class Rule1Result {
private String result;
private String level;
private String approval;
}
3.準備Drools Resource,在src/main/resources下新增兩個目錄:
META-INF和rules
META-INF下新增kmodule.xml
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="Product1_rules" packages="rules.product1">
<ksession name="rulesSession" />
</kbase>
</kmodule>
rules目錄下新增對應package的規則檔案DRL(rules/product1/rule1.drl)與Excel(rules/eproduct1/excelrule.xls)
rule1的規則是25-65歲的女性由主管審核,若為50歲以上者則由經理審核並提升到高等級
package rules.product1
import com.mytest.springbootdroolstest.model.UserData
import com.mytest.springbootdroolstest.model.Rule1Result
rule "female rule 1"
dialect "mvel"
when
$pdata : UserData( age >= 25 && age <= 65 && gender == "female" )
$rr : Rule1Result( result == "false" )
then
modify ( $rr ) { result = "true", approval = "supervisor 1" }
end
rule "female rule 2"
dialect "mvel"
when
$pdata : UserData( age >= 50 && gender == "female" )
$rr : Rule1Result( result == "true" )
then
modify ( $rr ) { level = "high 1", approval = "manager 1" }
end
excelrule的規則與rule1相同,只是審查性別為男性
4.Service,負責執行規則
package com.mytest.springbootdroolstest.droolsService;
@Service
public class Rule1Service {
private KieContainer kieContainer;
@Autowired
public Rule1Service() {
this.kieContainer = KieServices.Factory.get().getKieClasspathContainer();
}
public Rule1Result fireRule(UserData data1) {
// load knowledge base
KieSession kSession = kieContainer.newKieSession("rulesSession");
// prepare Result Model
Rule1Result result = new Rule1Result();
result.setResult("false");
result.setLevel("normal");
result.setApproval("staff");
// fire Rules
kSession.insert(data1); //外部傳入的資料
kSession.insert(result); //預計輸出的結果
int count = kSession.fireAllRules();// 執行規則
System.out.println(count + " rules implemented.");
kSession.dispose();
return result;
}
public Rule1Result fireRuleExcel(UserData data1) {
// load knowledge base
KieServices kieServices = KieServices.Factory.get();
Resource dt = ResourceFactory.newClassPathResource("rules/eproduct1/excelrule.xls", getClass());
KieFileSystem kieFileSystem = kieServices.newKieFileSystem().write(dt);
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
KieModule kieModule = kieBuilder.getKieModule();
KieSession kSession = kieServices.newKieContainer(kieModule.getReleaseId()).newKieSession();
// prepare Result Model
Rule1Result result = new Rule1Result();
result.setResult("false");
result.setLevel("normal");
result.setApproval("staff");
// fire Rules
kSession.insert(data1); //外部傳入的資料
kSession.insert(result); //預計輸出的結果
int count = kSession.fireAllRules();// 執行規則
System.out.println(count + " excel rules implemented.");
kSession.dispose();
return result;
}
}
5.Control,REST API處理
package com.mytest.springbootdroolstest.controller;
@RestController
@RequestMapping("/api")
public class Rule1Controller {
private final Rule1Service rule1Service;
@Autowired
public Rule1Controller(Rule1Service rule1Service) {
this.rule1Service = rule1Service;
}
@PostMapping("/rule1")
public Rule1Result fireRule1(@Valid @RequestBody UserData user) {
System.out.println("run fireRule1 : user(" + user.getName() + ")");
Rule1Result result = rule1Service.fireRule(user);
return result;
}
@PostMapping("/xlsrule1")
public Rule1Result fireXlsRule1(@Valid @RequestBody UserData user) {
System.out.println("run fireXlsRule1 : user(" + user.getName() + ")");
Rule1Result result = rule1Service.fireRuleExcel(user);
return result;
}
}
6.SpringBootApplication,啟動專案
package com.mytest.springbootdroolstest;
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
三、測試
1.以Postman發送要求測試規則
發送UserData為55歲女性時,以DRL描述的規則做判斷
實現兩條規則(female rule 1,female rule 2)
發送UserData為55歲男性時,以DRL描述的規則做判斷
沒有任何規則實現
發送UserData為55歲女性時,以Excel描述的規則做判斷
沒有任何規則實現
發送UserData為55歲男性時,以Excel描述的規則做判斷
實現一條規則(male rule 1)
以Drools作規則判斷,Drools也提供了DRL與Excel做規則描述提供程式寫作者與業務分析相當便利的工具(雖然Excel寫法都快趨近於程式寫作了),在規則單純下DRL與Excel的判斷結果都有差異(在網路上的範例多為單一Model的判斷,我用兩個Model做測試),實際運用還是就一種規則描述來撰寫才容易框架住範圍,結果也比較容易符合預期。
參考資料:
https://docs.jboss.org/drools/release/latestFinal/drools-docs/html_single/
https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/303916/
https://blog.csdn.net/wo541075754/article/details/74651552
https://tomoya92.github.io/2019/07/09/drools-tutorial/