What&Why Drools?
Drools(JBoss Rules )
的前身是Codehaus的一個開源項目叫Drools,後來納入JBoss門下,更名爲JBoss Rules,成爲了JBoss
應用服務器的規則引擎。- Drools是一個易於訪問企業策略、易於調整以及易於管理的
開源業務規則引擎
,符合業內標準,特點就是速度快
、效率高
。業務分析師或審覈人員可以利用它輕鬆查看業務規則,從而檢驗是否已編碼的規則執行了所需的業務規則。 - 還在使用複雜的
JAVA代碼
校驗複雜的優惠券/超市打折/計價規則/商品定價/階梯定價/遊戲規則/業務規則
?試試用Drools來解救代碼吧,適用但不僅僅包含以上場景。 - 相關資料 Drools Docs,Drools Spring Integration
KIE是什麼?
跟Drools相關,一直有一個叫KIE的東西,會經常見到,這裏有必要解釋一下。
KIE
(Knowledge Is Everything,知識就是一切。我們用的包是其中針對Spring優化的Kie-Spring
)是一個傘形項目,旨在將我們的相關技術集中在一起。它也是我們項目之間共享的核心
。
-
KIE包含以下不同但相關的項目,爲業務自動化和管理提供完整的解決方案組合:
-
Drools是一個
業務規則管理系統
,具有前向鏈接和後向鏈接推理的規則引擎,允許快速可靠地評估業務規則和複雜的事件處理。規則引擎也是創建專家系統的基本構建塊,在人工智能中,該專家系統是模擬人類專家的決策能力的計算機系統。 -
jBPM是一個靈活的
業務流程管理套件
,允許您通過描述爲實現這些目標而需要執行的步驟來建模您的業務目標。 -
OptaPlanner是一種
約束求解器
,可優化員工排班,車輛路徑,任務分配和雲優化等用例。 -
Business Central是一個
功能齊全的Web應用程序
,用於自定義業務規則和流程的可視化組合。 -
UberFire是一個基於
Web的工作臺框架
,受Eclipse富客戶端平臺的啓發。
Drools語言
rule "name"
attributes
when
LHS
Then
RHS
end
一個規則通常包括四個部分:
- 規則名稱(rule name)
- 屬性部分(attribute)
- 條件部分(LHS)
- 結果處理部分(RHS)
也就是說一大堆的if-else都是不需要的,用規則通殺一切。
條件連接符
對於對象內部的多個約束/條件的連接,可以採用 &&
(and)、||
(or) 和 ,
(and)來進行實現。
雖然&&和,運算符具有相同的語義,但它們的解析具有不同的優先級: &&
運算符在 ||
之前。並且 ,
與 &&
和||
不能混合使用,也就是說在有&&
或||
出現的LHS 當中,是不可以有 ,
連接符出現,反之亦然。
比較操作符
共提供了十二種類型的比較操作符,分別是:>
、>=
、<
、<=
、= =
、!=
、contains
、not contains
、memberof
、not memberof
、matches
、not matches
;
取值/賦值
-
$taxiRide:TaxiRide(distanceInMile <= 3)
可以獲取傳遞進來的對象taxiRide,用$taxiRide.getDistanceInMile()
則可以獲取到對象裏面的屬性值 -
如果要直接給傳遞進來的對象賦值,則要用
:=
進行處理,用=
是沒用的,當然,對於普通的操作則無需特殊處理。
背景
下面的項目會用到這個規則,這裏作爲一個背景簡單介紹一下,我們只搞起步價,續租價,返空費
三個。
A市巡遊出租汽車收費標準2018:
(一)起步價:首3公里12元;
(二)續租價:超過3公里部分,每公里2.6元;
(三)候時費:巡遊車營運時速低於10公里,每小時44元;
(四)返空費實行階梯附加,15至25公里按照續租價加收20%,25公里以上按續租價加收50%;
(五)夜間服務費(23:00-次日5:00),按續租價加收30%。
項目結構
Maven依賴
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.24.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.24.0.Final</version>
</dependency>
Application.yml
- Application.yml,並沒有什麼要配置的,當然,可以把自己一些變量配置進來再用@Value去取。
server:
port: 9999
servlet:
context-path: /drools
spring:
http:
encoding:
force: true
charset: UTF-8
application:
name: spring-cloud-study-drools
Config配置文件
- TaxiFareConfiguration
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.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.softdev.system.demo.service")
public class TaxiFareConfiguration {
private static final String drlFile = "TAXI_FARE_RULE.drl";
@Bean
public KieContainer kieContainer() {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(drlFile));
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
KieModule kieModule = kieBuilder.getKieModule();
return kieServices.newKieContainer(kieModule.getReleaseId());
}
}
Entity
兩個實體:
- TaxiFare 打車費。
import java.math.BigDecimal;
import lombok.Data;
@Data
public class TaxiFare {
private BigDecimal nightSurcharge;
private BigDecimal rideFare;
public BigDecimal total() {
return this.nightSurcharge.add(this.rideFare);
}
}
- TaxiRide 打車情況,isNightSurcharge是否夜間,distanceInMile打車距離。
import java.math.BigDecimal;
import lombok.Data;
@Data
public class TaxiRide {
private Boolean isNightSurcharge;
private BigDecimal distanceInMile;
}
Service
- TaxiFareCalculatorService
import java.math.BigDecimal;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.softdev.system.demo.entity.TaxiFare;
import com.softdev.system.demo.entity.TaxiRide;
@Service
public class TaxiFareCalculatorService {
@Autowired
private KieContainer kieContainer;
public BigDecimal calculateFare(TaxiRide taxiRide, TaxiFare rideFare) {
KieSession kieSession = kieContainer.newKieSession();
kieSession.setGlobal("rideFare", rideFare);
kieSession.insert(taxiRide);
kieSession.fireAllRules();
kieSession.dispose();
return rideFare.total();
}
}
Controller
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.softdev.system.demo.entity.TaxiFare;
import com.softdev.system.demo.entity.TaxiRide;
import com.softdev.system.demo.service.TaxiFareCalculatorService;
@RequestMapping("taxi")
@RestController
/**
* Drools的士計費控制器
* (起步價:首3公里12元; 續租價:超過3公里部分,每公里2.6元;返空費實行階梯附加,15至25公里按照續租價加收20%,25公里以上按續租價加收50%; )
* @author zhengkai.blog.csdn.net
* */
public class TaxiController {
@Autowired
private TaxiFareCalculatorService taxiFareCalculatorService;
@GetMapping("cal")
public ResponseEntity<Object> calTaxiFare(TaxiRide taxiRide){
//http://localhost:9999/drools/taxi/cal
if(taxiRide.getIsNightSurcharge()==null) {taxiRide.setIsNightSurcharge(false);}
if(taxiRide.getDistanceInMile()==null) {taxiRide.setDistanceInMile(new BigDecimal(9));}
TaxiFare rideFare = new TaxiFare();
//Drools計算
BigDecimal totalCharge = taxiFareCalculatorService.calculateFare(taxiRide, rideFare);
//BigDecimal處理:設置一位小數位,向上取整,去掉末尾0,,轉換爲String
String totalString = totalCharge.setScale(1,BigDecimal.ROUND_UP).stripTrailingZeros().toPlainString();
return ResponseEntity.ok(totalString);
}
}
Drools規則文件
- TAXI_FARE_RULE.drl ,放在
resources
目錄下即可
global com.softdev.system.demo.entity.TaxiFare rideFare;
import com.softdev.system.demo.entity.TaxiRide;
import java.math.BigDecimal;
dialect "mvel"
/**
* 的士打表計價Drools
* @Author by zhengkai.blog.csdn.net
*/
rule "Calculate Taxi Fare - Output "
salience 100
when
$taxiRide:TaxiRide();
then
System.out.println("#公里數 : "+$taxiRide.getDistanceInMile);
System.out.println("#起步價 : "+12);
rideFare.setNightSurcharge(BigDecimal.ZERO);
rideFare.setRideFare(BigDecimal.ZERO);
end
rule "Calculate Taxi Fare - Less than three kilometers"
salience 99
when
//起步價:首3公里12元; 不論白天黑夜 || distanceInMile = 3
$taxiRide:TaxiRide(distanceInMile <= 3);
then
rideFare.setRideFare(new BigDecimal(12));
end
rule "Calculate Taxi Fare - 3 ~ 15 kilometers"
salience 99
when
//續租價:超過3公里部分,每公里2.6元; 非夜間
$taxiRide:TaxiRide( isNightSurcharge == false , distanceInMile > 3 , distanceInMile <= 15);
then
BigDecimal secondFare = ($taxiRide.getDistanceInMile().subtract(new BigDecimal(3))).multiply(new BigDecimal(2.6));
System.out.println("#續租價 : "+secondFare);
rideFare.setRideFare(new BigDecimal(12).add(secondFare));
System.out.println("#應付金額 : "+rideFare.getRideFare());
end
rule "Calculate Taxi Fare - 15~25 kilometers"
salience 99
when
//續租價:超過3公里部分,每公里2.6元;
//返空費實行階梯附加,15至25公里按照續租價加收20%
$taxiRide:TaxiRide( isNightSurcharge == false , distanceInMile > 15 , distanceInMile <= 25);
then
BigDecimal secondFare = ($taxiRide.getDistanceInMile().subtract(new BigDecimal(15))).multiply(new BigDecimal(2.6)).multiply(new BigDecimal(1.2));
System.out.println("#續租價 : "+secondFare);
//12+13x2.6
rideFare.setRideFare(new BigDecimal(45.8).add(secondFare));
System.out.println("#應付金額 : "+rideFare.getRideFare());
end
測試規則
#公里數 : 18
#起步價 : 12
#續租價 : 9.3599999999999999733546474089962311969192616506432313240715207582065549019034733646549284458160400390625
#應付金額 (加上前15公里45.8元): 55.1599999999999971311837043685954877124221913381432313240715207582065549019034733646549284458160400390625
#瀏覽器輸出:55.2
#公里數 : 2
#起步價 : 12
#瀏覽器輸出:12
#公里數 : 3.5
#起步價 : 12
#續租價 : 1.3000000000000000444089209850062616169452667236328125
#應付金額 : 13.3000000000000000444089209850062616169452667236328125
#瀏覽器輸出:13.3