#SpringBoot #Drools #規則引擎 #RESTful API

SpringBoot + Drools整合並實作為RESTful API

吳冠霆 Tyng Wu 2020/11/27 18:00:00
2708

一、前言

因為最近需要分析個人借貸系統,稍微看了一下規則引擎Drools,再加上使用REST Web API的趨勢,決定使用SpringBoot+Drools來構建簡單的WebService範例供個人借貸系統呼叫使用。規則的部分則使用Drools的規則描述語言DRL(Drools Rule Language)Excel來描述。

二、實作

1.建立maven專案,在pom.xml中加入SpringBootDrools相關的依賴項目,為了簡化程式碼與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,也是DroolsModel

 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.ControlREST 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發送要求測試規則

發送UserData55歲女性時,以DRL描述的規則做判斷

 

 實現兩條規則(female rule 1,female rule 2)

發送UserData55歲男性時,以DRL描述的規則做判斷

 

沒有任何規則實現

發送UserData55歲女性時,以Excel描述的規則做判斷

 

沒有任何規則實現

發送UserData55歲男性時,以Excel描述的規則做判斷

 

實現一條規則(male rule 1)

 

Drools作規則判斷,Drools也提供了DRLExcel做規則描述提供程式寫作者與業務分析相當便利的工具(雖然Excel寫法都快趨近於程式寫作了),在規則單純下DRLExcel的判斷結果都有差異(在網路上的範例多為單一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/

 

吳冠霆 Tyng Wu