drools規(guī)則動態(tài)化實(shí)踐解析
一 、 規(guī)則引擎業(yè)務(wù)應(yīng)用背景
業(yè)務(wù)邏輯中經(jīng)常會有一些冗長的判斷,需要寫特別多的if else,或者一些判斷邏輯需要經(jīng)常修改。這部分邏輯如果以java代碼來實(shí)現(xiàn),會面臨代碼規(guī)??刂撇蛔?,經(jīng)常需要修改邏輯上線等多個弊端。這時候我們就需要集成規(guī)則引擎對這些判斷進(jìn)行線上化的管理
二、規(guī)則引擎選型
目前開源的規(guī)則引擎也比較多,根據(jù)原有項目依賴以及短暫接觸過的規(guī)則引擎,我們著重了解了一下幾個
drools:
-社區(qū)活躍,持續(xù)更新
-使用廣泛
-復(fù)雜
-學(xué)習(xí)成本較高
easy-rule:
-簡單易學(xué)
-滿足使用訴求
-長時間未發(fā)布新版
easycode:
-京東物流同事維護(hù)的平臺
-基于flowable dmn實(shí)現(xiàn)
-配置簡單直觀
-已有大量系統(tǒng)使用
總結(jié):
- 簡單配置型規(guī)則可以接入easycode,平臺提供配置頁面,通過jsf交互。
- 復(fù)雜規(guī)則,需要動態(tài)生成規(guī)則,easycode目前還不支持。drools從流行度及活躍度考慮,都比easy-rule強(qiáng),所以選擇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 總結(jié)
如上,我們簡單使用drools,僅需要注意drl文件語法。根據(jù)drl文件生成規(guī)則的工作內(nèi)存,通過KieSession
或者StatelessKieSession
與工作內(nèi)存交互。整個流程并不復(fù)雜。注意 KieHelper
僅是在演示中簡單使用,demo中包含使用bean來管理容器的方式,即便在簡單使用場景也不應(yīng)通過 KieHelper
來重復(fù)加載規(guī)則。
但是,這樣并不能滿足我們線上化判斷,或者頻繁更改規(guī)則的訴求。于是我們在實(shí)踐中需要對drools更高階的使用方式。
四、 drools動態(tài)化實(shí)踐
從以上簡單demo中我們可以看出,規(guī)則依賴drl文件存在。而業(yè)務(wù)實(shí)際使用中,需要動態(tài)對規(guī)則進(jìn)行修改,無法直接使用drl文件。
以下是我了解過的四種動態(tài)的方案:
- drt文件,創(chuàng)建模板,動態(tài)生成drl文件,也是我們目前所用的方式。
- excel文件導(dǎo)入,實(shí)際上和模板文件類似,依然無法直接交給業(yè)務(wù)人員來使用。
- 自己拼裝String,動態(tài)生成drl文件,網(wǎng)上大多數(shù)博文使用方式,過于原始。
- api方式,drools的api方式復(fù)雜,使用需要對drl文件有足夠的了解。
最后介紹以下drools在項目中的實(shí)際使用方式
4.1 配置規(guī)則
我們的業(yè)務(wù)場景可以理解為多個緩沖池構(gòu)成的一個網(wǎng)狀結(jié)構(gòu)。
示例如下:
上圖中每個方塊為一個緩沖池,每條連線為一條從A緩沖池流向B緩沖池的規(guī)則。實(shí)際場景中緩沖池有數(shù)百個,絕大多數(shù)都有自己的規(guī)則,這些規(guī)則構(gòu)成一張復(fù)雜的網(wǎng)絡(luò)?;跇I(yè)務(wù)訴求,緩沖池的流向規(guī)則需要經(jīng)常變動,我們需要在業(yè)務(wù)中能動態(tài)改變這些連線的條件,或者改變連線。在這種情況下,如果使用靜態(tài)的drl文件來實(shí)現(xiàn)這些規(guī)則,需要數(shù)百規(guī)則文件,維護(hù)量大,且每次修改后使規(guī)則生效的代價較大。在此背景下,我們嘗試drools高階應(yīng)用,既規(guī)則動態(tài)化實(shí)踐。
我們在創(chuàng)建緩沖池的頁面中加入了流向規(guī)則的創(chuàng)建環(huán)節(jié)。每個緩沖池維護(hù)自己的流向規(guī)則,即為自己的一根連線。如下圖:
4.2 動態(tài)生成drl
drt文件內(nèi)容:
(實(shí)際業(yè)務(wù)模板中比這個復(fù)雜,有一定校驗及業(yè)務(wù)邏輯,此處做了簡化)
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ù) 標(biāo)識當(dāng)前的規(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ù)一個隊列及模板的路徑進(jìn)行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)存中。實(shí)際上我們不可能在每次匹配中重新加載所有規(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
新增一個文件來進(jìn)行加載,代價小,但是同步各個實(shí)例的代價較大。
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
則需要在使用完成后進(jìn)行關(guān)閉
kieSession.insert(clueModel); kieSession.fireAllRules(); kieSession.dispose();
在執(zhí)行規(guī)則的過程中可以加入各種監(jiān)聽器對過程中各種變化進(jìn)行監(jiān)聽。篇幅原因交給各位去探索。
五、 總結(jié)
從上邊的流程中我們體驗了動態(tài)規(guī)則的創(chuàng)建以及使用。動態(tài)規(guī)則滿足了我們規(guī)則動態(tài)變化,規(guī)則統(tǒng)一管理的訴求。
我也總結(jié)了在這種使用方式下drools的幾個優(yōu)缺點(diǎn)。
優(yōu)點(diǎn):
- 規(guī)則動態(tài)化方便
- 在工作內(nèi)存中匹配規(guī)則性能好
- 幾乎可以滿足所有的規(guī)則需求
- 內(nèi)置方法豐富完善
缺點(diǎn):
- 分布式一致性需要自行處理
- 需要研發(fā)了解drl語法
- 學(xué)習(xí)曲線陡峭
- 匹配過程監(jiān)控手段需要自行實(shí)現(xiàn)
以上就是drools規(guī)則動態(tài)化實(shí)踐解析的詳細(xì)內(nèi)容,更多關(guān)于drools規(guī)則動態(tài)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
不使用myeclipse注冊機(jī)得到myeclipse注冊碼的方法(myeclipse序列號)
本文為大家介紹不使用myeclipse注冊機(jī)就能得到myeclipse注冊碼(序列號)的方法, 運(yùn)行下面的JAVA代碼就可以了2014-01-01Spring中@EnableScheduling注解的工作原理詳解
這篇文章主要介紹了Spring中@EnableScheduling注解的工作原理詳解,@EnableScheduling是 Spring Framework 提供的一個注解,用于啟用Spring的定時任務(wù)(Scheduling)功能,需要的朋友可以參考下2024-01-01java讀取word文檔,提取標(biāo)題和內(nèi)容的實(shí)例
這篇文章主要介紹了java讀取word文檔,提取標(biāo)題和內(nèi)容的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10spring boot+vue 的前后端分離與合并方案實(shí)例詳解
這篇文章主要介紹了spring boot+vue 的前后端分離與合并方案實(shí)例詳解,需要的朋友可以參考下2017-11-11SpringSecurity數(shù)據(jù)庫進(jìn)行認(rèn)證和授權(quán)的使用
本文主要介紹了用戶的賬號、密碼以及角色信息在數(shù)據(jù)庫中的認(rèn)證和授權(quán),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08