drools規(guī)則動態(tài)化實踐解析
一 、 規(guī)則引擎業(yè)務應用背景
業(yè)務邏輯中經(jīng)常會有一些冗長的判斷,需要寫特別多的if else,或者一些判斷邏輯需要經(jīng)常修改。這部分邏輯如果以java代碼來實現(xiàn),會面臨代碼規(guī)??刂撇蛔。?jīng)常需要修改邏輯上線等多個弊端。這時候我們就需要集成規(guī)則引擎對這些判斷進行線上化的管理
二、規(guī)則引擎選型
目前開源的規(guī)則引擎也比較多,根據(jù)原有項目依賴以及短暫接觸過的規(guī)則引擎,我們著重了解了一下幾個
drools:
-社區(qū)活躍,持續(xù)更新
-使用廣泛
-復雜
-學習成本較高
easy-rule:
-簡單易學
-滿足使用訴求
-長時間未發(fā)布新版
easycode:
-京東物流同事維護的平臺
-基于flowable dmn實現(xiàn)
-配置簡單直觀
-已有大量系統(tǒng)使用
總結:
- 簡單配置型規(guī)則可以接入easycode,平臺提供配置頁面,通過jsf交互。
- 復雜規(guī)則,需要動態(tài)生成規(guī)則,easycode目前還不支持。drools從流行度及活躍度考慮,都比easy-rule強,所以選擇drools。
三、 drools簡單示例
3.1 引入依賴
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${drools.version}</version>
</dependency>
3.2 寫drl文件
我們寫一個簡單的demo
規(guī)則為:
匹配一個sku對象
0<價格<=100 10積分
100<價格<=1000 100積分
1000<價格<=10000 1000積分
在resources文件夾下新增 rules/skupoints.drl 文件 內(nèi)容如下
package com.example.droolsDemo
import com.example.droolsDemo.bean.Sku;
// 10積分
rule "10_points"
when
$p : Sku( price > 0 && price <= 100 )
then
$p.setPoints(10);
System.out.println("Rule name is [" + drools.getRule().getName() + "]");
end
// 100積分
rule "100_points"
when
$p : Sku( price > 100 && price <= 1000 )
then
$p.setPoints(100);
System.out.println("Rule name is [" + drools.getRule().getName() + "]");
end
// 1000積分
rule "1000_points"
when
$p : Sku( price > 1000 && price <= 10000 )
then
$p.setPoints(1000);
System.out.println("Rule name is [" + drools.getRule().getName() + "]");
end
3.3 使用起來
@Test
public void testOneSku() {
Resource resource = ResourceFactory.newClassPathResource("rules/skupoints.drl");
KieHelper kieHelper = new KieHelper();
kieHelper.addResource(resource);
KieBase kieBase = kieHelper.build();
KieSession kieSession = kieBase.newKieSession();
Sku sku1 = new Sku();
sku1.setPrice(10);
kieSession.insert(sku1);
int allRules = kieSession.fireAllRules();
kieSession.dispose();
System.out.println("sku1:" + JSONObject.toJSONString(sku1));
System.out.println("allRules:" + allRules);
}
@Test
public void testOneSku2() {
Resource resource = ResourceFactory.newClassPathResource("rules/skupoints.drl");
KieHelper kieHelper = new KieHelper();
kieHelper.addResource(resource);
KieBase kieBase = kieHelper.build();
StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
Sku sku1 = new Sku();
sku1.setPrice(10);
statelessKieSession.execute(sku1);
System.out.println("sku1:" + JSONObject.toJSONString(sku1));
}
3.4 輸出


3.5 總結
如上,我們簡單使用drools,僅需要注意drl文件語法。根據(jù)drl文件生成規(guī)則的工作內(nèi)存,通過KieSession或者StatelessKieSession與工作內(nèi)存交互。整個流程并不復雜。注意 KieHelper僅是在演示中簡單使用,demo中包含使用bean來管理容器的方式,即便在簡單使用場景也不應通過 KieHelper來重復加載規(guī)則。
但是,這樣并不能滿足我們線上化判斷,或者頻繁更改規(guī)則的訴求。于是我們在實踐中需要對drools更高階的使用方式。
四、 drools動態(tài)化實踐
從以上簡單demo中我們可以看出,規(guī)則依賴drl文件存在。而業(yè)務實際使用中,需要動態(tài)對規(guī)則進行修改,無法直接使用drl文件。
以下是我了解過的四種動態(tài)的方案:
- drt文件,創(chuàng)建模板,動態(tài)生成drl文件,也是我們目前所用的方式。
- excel文件導入,實際上和模板文件類似,依然無法直接交給業(yè)務人員來使用。
- 自己拼裝String,動態(tài)生成drl文件,網(wǎng)上大多數(shù)博文使用方式,過于原始。
- api方式,drools的api方式復雜,使用需要對drl文件有足夠的了解。
最后介紹以下drools在項目中的實際使用方式
4.1 配置規(guī)則
我們的業(yè)務場景可以理解為多個緩沖池構成的一個網(wǎng)狀結構。
示例如下:

上圖中每個方塊為一個緩沖池,每條連線為一條從A緩沖池流向B緩沖池的規(guī)則。實際場景中緩沖池有數(shù)百個,絕大多數(shù)都有自己的規(guī)則,這些規(guī)則構成一張復雜的網(wǎng)絡。基于業(yè)務訴求,緩沖池的流向規(guī)則需要經(jīng)常變動,我們需要在業(yè)務中能動態(tài)改變這些連線的條件,或者改變連線。在這種情況下,如果使用靜態(tài)的drl文件來實現(xiàn)這些規(guī)則,需要數(shù)百規(guī)則文件,維護量大,且每次修改后使規(guī)則生效的代價較大。在此背景下,我們嘗試drools高階應用,既規(guī)則動態(tài)化實踐。
我們在創(chuàng)建緩沖池的頁面中加入了流向規(guī)則的創(chuàng)建環(huán)節(jié)。每個緩沖池維護自己的流向規(guī)則,即為自己的一根連線。如下圖:

4.2 動態(tài)生成drl
drt文件內(nèi)容:
(實際業(yè)務模板中比這個復雜,有一定校驗及業(yè)務邏輯,此處做了簡化)
template header
// 模板需要使用的參數(shù)
id
cluePoolId
sourceList
cooperateTypeList
regionId
secondDepartmentId
battleId
outCluePoolId
amountCompareFlag
amount
salience
package rulePoolOut
// 全局對象
global java.util.List list;
global java.util.List stopIdList;
global java.util.List ruleIdList;
// 引入的java類
import com.example.drools.bean.ClueModel
import org.springframework.util.CollectionUtils
import org.apache.commons.lang3.StringUtils;
import java.lang.Long
template "CluePoolOut"
// 規(guī)則名稱
rule "clue_pool_@{cluePoolId}_@{id}"
// 參數(shù) 標識當前的規(guī)則是否不允許多次循環(huán)執(zhí)行
no-loop true
// 參數(shù) 優(yōu)先級
salience @{salience}
// 參數(shù) 規(guī)則組 本組規(guī)則只能有一個生效
activation-group "out_@{cluePoolId}"
// 匹配的LHS
when
$clue:ClueModel(cluePoolId == @{cluePoolId})
ClueModel(CollectionUtils.isEmpty(@{sourceList}) || source memberOf @{sourceList})
ClueModel(CollectionUtils.isEmpty(@{cooperateTypeList}) || cooperateType memberOf @{cooperateTypeList})
ClueModel(secondDepart == @{secondDepartmentId})
ClueModel(regionNo == @{regionId})
ClueModel(battleId == @{battleId})
ClueModel(null != estimateOrderCount && (Long.valueOf(estimateOrderCount) @{amountCompareFlag} Long.valueOf(@{amount})))
// 如果配置要執(zhí)行的RHS 支持java語法
then
ruleIdList.add(@{id});
$clue.setCluePoolId(Long.valueOf(@{outCluePoolId}));
list.add(@{outCluePoolId});
update($clue);
}
end
end template
生成drl內(nèi)容: 根據(jù)一個隊列及模板的路徑進行drl內(nèi)容的生成
List<CrmCluePoolDistributeRuleBusinessBattleVO> ruleCenterVOS = new ArrayList<>();
CrmCluePoolDistributeRuleBusinessBattleVO vo = new CrmCluePoolDistributeRuleBusinessBattleVO();
vo.setCooperateTypeList(Lists.newArrayList(1, 2, 4));
vo.setAmountCompareFlag(">");
vo.setAmount(100L);
ruleCenterVOS.add(vo);
String drl = droolsManager.createDrlByTemplate(ruleCenterVOS, "rules/CluePoolOutRuleTemplate.drt");
public String createDrlByTemplate(Collection<?> objects, String path) {
ObjectDataCompiler compiler = new ObjectDataCompiler();
try (InputStream dis = ResourceFactory.newClassPathResource(path, this.getClass()).getInputStream()) {
return compiler.compile(objects, dis);
} catch (IOException e) {
log.error("創(chuàng)建drl文件失敗!", e);
}
return null;
}
4.3 加載drl
上邊的簡單示例中,我們使用了KieHelper 來加載規(guī)則文件至工作內(nèi)存中。實際上我們不可能在每次匹配中重新加載所有規(guī)則文件,所以我們可以單例的使用規(guī)則容器,通過以下方式或者也可以使用@Bean等方式來管理容器。
private final KieServices kieServices = KieServices.get(); // kie文件系統(tǒng),需要緩存,如果每次添加規(guī)則都是重新new一個的話,則可能出現(xiàn)問題。即之前加到文件系統(tǒng)中的規(guī)則沒有了 private final KieFileSystem kieFileSystem = kieServices.newKieFileSystem(); // 需要全局唯一一個 private KieContainer kieContainer;
通過將內(nèi)容寫入 kieFileSystem然后重新加載整個 kieBase即可重新加載規(guī)則,但是這種行為比較重,代價較大
也可以通過 kieBase新增一個文件來進行加載,代價小,但是同步各個實例的代價較大。
KnowledgeBaseImpl kieBase = (KnowledgeBaseImpl)kieContainer.getKieBase(kieBaseName);
KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
Resource resource = ResourceFactory.newReaderResource(new StringReader(ruleContent));
builder.add(resource,ResourceType.DRL);
if (builder.hasErrors()) {
throw new RuntimeException("增加規(guī)則失敗!" + builder.getErrors().toString());
}
kieBase.addPackages(builder.getKnowledgePackages());
4.4 匹配
通過 StatelessKieSession與規(guī)則引擎交互
// 獲取一個鏈接
StatelessKieSession kieSession = droolsManager.getStatelessKieSession(RuleTemplateEnum.CLUE_POOL_OUT_RULE.getKieBaseName());
// 創(chuàng)建全局變量對象
List<Long> list = new ArrayList<>();
List<Long> stopIdList = Lists.newArrayList();
List<String> result = new ArrayList<>();
List<Long> ruleIdList = new ArrayList<>();
// 塞入全局變量
kieSession.setGlobal("ruleIdList", ruleIdList);
kieSession.setGlobal("list", list);
kieSession.setGlobal("stopIdList", stopIdList);
kieSession.setGlobal("result", result);
// 執(zhí)行規(guī)則
kieSession.execute(clueModel);
如果使用 KieSession則需要在使用完成后進行關閉
kieSession.insert(clueModel); kieSession.fireAllRules(); kieSession.dispose();
在執(zhí)行規(guī)則的過程中可以加入各種監(jiān)聽器對過程中各種變化進行監(jiān)聽。篇幅原因交給各位去探索。
五、 總結
從上邊的流程中我們體驗了動態(tài)規(guī)則的創(chuàng)建以及使用。動態(tài)規(guī)則滿足了我們規(guī)則動態(tài)變化,規(guī)則統(tǒng)一管理的訴求。
我也總結了在這種使用方式下drools的幾個優(yōu)缺點。
優(yōu)點:
- 規(guī)則動態(tài)化方便
- 在工作內(nèi)存中匹配規(guī)則性能好
- 幾乎可以滿足所有的規(guī)則需求
- 內(nèi)置方法豐富完善
缺點:
- 分布式一致性需要自行處理
- 需要研發(fā)了解drl語法
- 學習曲線陡峭
- 匹配過程監(jiān)控手段需要自行實現(xiàn)
以上就是drools規(guī)則動態(tài)化實踐解析的詳細內(nèi)容,更多關于drools規(guī)則動態(tài)化的資料請關注腳本之家其它相關文章!
相關文章
不使用myeclipse注冊機得到myeclipse注冊碼的方法(myeclipse序列號)
本文為大家介紹不使用myeclipse注冊機就能得到myeclipse注冊碼(序列號)的方法, 運行下面的JAVA代碼就可以了2014-01-01
Spring中@EnableScheduling注解的工作原理詳解
這篇文章主要介紹了Spring中@EnableScheduling注解的工作原理詳解,@EnableScheduling是 Spring Framework 提供的一個注解,用于啟用Spring的定時任務(Scheduling)功能,需要的朋友可以參考下2024-01-01
spring boot+vue 的前后端分離與合并方案實例詳解
這篇文章主要介紹了spring boot+vue 的前后端分離與合并方案實例詳解,需要的朋友可以參考下2017-11-11
SpringSecurity數(shù)據(jù)庫進行認證和授權的使用
本文主要介紹了用戶的賬號、密碼以及角色信息在數(shù)據(jù)庫中的認證和授權,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08

