1. 背景介紹
1.1 何為規則引擎
很多企業的IT業務系統中,經常會有大量的業務規則配置,而且隨着企業管理者的決策變化,這些業務規則也會隨之發生更改,為了適應這樣的需求,IT業務系統應該能夠快速且低成本的更新,通常做法是將業務規則的配置單獨拿出來,使之與業務系統保持低耦合,實現這樣功能的程序,叫做規則引擎。
接受數據輸入,解釋業務規則,並根據業務規則作出業務決策,從而實現了將業務決策從應用程序中分離出來。
1.2 一個實際的例子
銀行貸款業務中,每種貸款類型都有不同的業務規則,並且這些規則也可能會根據實際應用情況進行調整,如覺得網貸產品類型有如下判定規則:
- 如果公積金繳存基數大於6000則進入白領貸
- 如果公積金繳存基數小於6000但單位性質是國家機關/事業單位也進入白領貸
- 如果公積金繳存基數小於6000且單位性質為非國家機關/事業單位則進入市民易貸
- 如果公積金繳存基數小於6000並且單位性質缺失則進入公積金貸
如果在代碼中處理這類業務邏輯,會有很多的IF/ELSE,並且如果規則發生變化,還需要重新編寫代碼、編譯、部署才能上線。
而通過規則引擎,可以方便的將這類業務強相關的邏輯放到規則引擎中執行
1.3 規則引擎的優點
對系統的使用人員
l 把業務策略(規則)的創建、修改和維護的權利交給業務經理
l 提高業務靈活性
l 加強業務處理的透明度,業務規則可以被管理
l 減少對IT人員的依賴程度
l 避免升級的風險
對IT開發人員
l 簡化系統架構,優化應用
l 提高系統的可維護性和維護成本
l 方便系統的整合
l 減少編寫“硬代碼”業務規則的成本和風險
2. Drools簡介
Drools是一款基於Java的開源規則引擎,可以將復雜多變的規則從硬編碼中解放出來,以規則腳本的形式存放在文件或特定的數據庫中,使得業務規則的變更不需要修改代碼即可在線上環境生效。
規則引擎由推理引擎發展而來,是一種嵌入在應用程序中的組件,實現了將業務決策從應用代碼中分離出來,並使用預定義的語義模塊編寫業務決策。接收數據輸入,解釋業務規則,並根據業務規則作出業務決策。
3. Drools基本概念
3.1 工作流程
需要判定的規則加載進ProductionMemory
需要判定的fact對象插入到WorkingMemory
推理引擎(Inference Engine)通過模式匹配器(Pattern Matcher)對規則和fact對象進行批評,如果有規則匹配成功,則將待執行的規則放入Agenda(議事日程)隊列中等待執行。
當Agenda隊列中的規則執行的過程中如果對WorkingMemory中的Fact對象進行了insert/update/delete操作時,需要重新進行推理判定,刷新Agenda隊列中的執行序列。
當Agenda隊列中所有規則執行完畢后,結束當前任務。
3.2 Fact對象
Fact對象指傳遞給Drools腳本的對象,是一個普通的POJO,可以對該Fact對象進行讀寫操作,並調用該對象的方法。
當一個POJO插入到WorkingMemory中變成Fact之后,Fact對象不是對原來POJO對象進行Clone,而是對原有對象的引用。規則通過對fact對象的讀寫,實現對應用數據的讀寫,對其中的屬性,需要提供get/set方法,通過這些getter 和setter 方法可以方便的實現對Fact 對象的讀寫操作,所以我們可以簡單的把Fact 對象理解為規則與應用系統數據交互的橋梁或通道。
規則中可以動態的在WorkingMemory中插入刪除新的fact對象。
3.3 Rete算法簡介
Rete算法是Charles Forgy博士在1979年的論文中首次提出的,針對基於規則知識表現的模式匹配算法。當前,大部分規則引擎都是基於Rete算法為核心。
Rete算法是一個用於產生式系統的高效模式匹配算法。在一個產生式系統中,被處理的數據叫做WorkingMemory,用於判定的規則分為兩個部分LHS(left-hand-side)和RHS(right hand side),分別表示前提和結論。主要流程可以分為以下步驟:
- Match:找出符合LHS部分的WorkingMemory集合
- Confilict resolution:選出一個條件被滿足的規則
- Act:執行RHS的內容
- 返回第一步
Rete算法主要改進Match的處理過程,通過構建一個網絡進行匹配。
3.4 基礎語法
Drools文件中最重要的是:包路徑、引用、規則體
一個DRL文件的例子HelloWorld.drl
package:包路徑。該路徑是邏輯路徑,可以隨便寫,但不能不寫,已.的方式隔開,規則文件中永遠是第一行
import:引用。導入規則文件需要使用到的外部變量,可以導入類,也可以是類中的靜態方法
rule:規則體。以rule開頭,以end結尾,每個文件可以包含多個rule,規則體分為3個部分:LHS、RHS、屬性。
LHS:(Left Hand Side),條件部分,在一個規則當中”when”和”then”中間的部分就是LHS部分,在LHS當中,可以包含0~N個條件,如果LHS為空的話,那么引擎會自動添加一個eval(true)的條件。
RHS:(Right Hand Side),是規則真正做事情的部分,滿足條件觸發動作的操作部分,在RHS中可以使用LHS部分當中定義的綁定變量名、設置的全局變量、或者是直接編寫的java代碼,也可以使用import的類,雖然可以,但不建議在RHS中有條件判斷。
RHS中可以使用insert/update/modify/delete實現對當前WorkingMemory中Fact對象的新增/修改/刪除。
3.5 KIE API
3.5.1什么是KIE
KIE是jBoss里面一些相關項目的統稱,下圖就是KIE代表的一些項目,其中我們比較熟悉的就有jBPM和Drools。
jBPM:工作流引擎
Drools:規則引擎
OptaPlanner:規划引擎
Guvnor:業務規划管理系統(Drools 6.0后已被WorkBench替換)
這些項目都有一定的關聯關系,並且存在一些通用的API,比如說涉及到構建(building)、部署(deploying)和加載(loading)等方面的,這些API就都會以KIE作為前綴來表示這些是通用的API。
總的來說,就是jBoss通過KIE將jBPM和Drools等相關項目進行了一個整合,統一了他們的使用方式。像KieServices這些KIE類就是整合后的結果,在Drools中這樣使用,在jBPM里面也是這樣使用。
在Drools當中,規則的編譯與運行要通過Drools提供的各種API來實現,這些API總體來講可以分為三類:規則編譯、規則收集和規則執行。完成這些工作的API主要有KnowledgeBuilder、KnowledgeBase、StatefulKnowledgeSession、StatelessKnowledgeSession等,它們起到了對規則文件進行收集、編譯、查錯、插入fact、設置global、執行規則或規則流等作用。
3.5.2 KnowledgeBuilder
KnowledgeBuilder的作用是用來在業務代碼當中收集已經編寫好的規則,然后對這些規則文件進行編譯,最終產生一批編譯好的規則包(KnowledgePackage)給其他的應用程序使用。
KnowledgeBuilder在編譯規則的時候可以通過其提供的hasErrors()方法得到編譯規則過程中發現規則是否有錯誤,如果有的話通過其提供的getErrors()方法將錯誤打印出來,以幫助我們找到規則當中的錯誤信息。
通過KnowledgeBuilder編譯的規則文件的類型有很多種,如.drl文件或一個xls文件等。產生的規則包可以是具體的規則文件行程的,也可以是規則流(rule flow)文件形成的,在添加規則文件時,需要通過使用ResourceType枚舉值來指定規則文件的類型;同時在指定規則文件的時候drools還提供了一個名為ResourceFactory的對象,通過該對象可以實現從Classpath、URL、File、ByteArray、Reader或諸如XLS的二進制文件里加載規則。
3.5.3 KnowledgeBase
KnowledgeBase是Drools提供的用來收集應用當中知識(Knowledge)定義的知識庫對象,在一個KnowledgeBase當中可以包含普通的規則(rule)、規則流(rule flow)、函數定義(function)、用戶自定義的對象(type model)等。
業務對象都是插入到由KnowledgeBase產生的兩種類型的session 對象當中(StatefulKnowledgeSession 和StatelessKnowledgeSession,后面會有對應的章節對這兩種類型的對象進行介紹),通過session 對象可以觸發規則執行或開始一個規則流執行。
KnowledgeBase 創建完成之后,接下來就可以將我們前面使用KnowledgeBuilder 生成的KnowledgePackage 的集合添加到KnowledgeBase 當中。
3.5.4 StatefulKnowledgeSession
StatefulKnowledgeSession 對象是一種最常用的與規則引擎進行交互的方式,它可以與規則引擎建立一個持續的交互通道,在推理計算的過程當中可能會多次觸發同一數據集。接收外部插入的數據fact對象(POJO),將編譯好的規則包和業務數據通過fireAllRules()方法觸發所有的規則執行。使用完成需調用dispose()方法以釋放相關內存資源。
3.5.5 StatelessKnowledgeSession
對StatefulKnowledgeSession的封裝實現,與其對比不需要調用dispose()方法釋放內存,只能插入一次fact對象,StatelessKieSession隔離了每次與規則引擎的交互,不會維護會話狀態。
4. Drools詳細介紹
4.1 Drl
在Drools當中,一個標准的規則文件就是一個以”.drl”結尾的文本文件,由於它是一個標准的文本文件,所以可以通過一些記事本工具對其進行打開、查看和編輯。規則是放在規則文件當中的,一個規則文件可以存放多個規則,除此之外,在規則文件當中還可以存放用戶自定義的函數、數據對象及自定義查詢等相關在規則當中可能會用到的一些對象。
一個標准的規則文件的結構代碼:
package package-name(包名,必須的,用於邏輯上的管理,若自定義查詢或函數屬於同一個包名,不管物理位置如何,都可以調用),package在規則文件中是第一行,其他的順序可以是無序的
import(需要導入的類名)
globals(全局變量)
functions(函數)
queries(查詢)
rules(規則,可以多個)
一個規則包含三部分:只有attributes部分可選,其他都是必填
4.1.1 屬性attributes
定義當前規則執行的一些屬性等,比如是否可被重復執行,過期時間,生效時間等。
Salience 優先級
作用:設置規則執行的優先級,值是一個數字,數字越大執行的優先級越高,它的值可以是一個負數,默認值是0。如果我們不手動設置salience屬性值,則執行順序是隨機的。
no-loop 防止死循環
在一個規則中如果條件滿足就對Working Memory當中的某個Fact對象進行修改,比如使用update將其更新到當前的Working Memory當中,這時候引擎會再次檢查所有的規則是否滿足條件,如果滿足會再執行,可能會出現死循環。
作用:用來控制已經執行過的規則條件再次滿足時是否再次執行,默認是false,如果屬性值是true,表示該規則只會被規則引擎檢查一次。
lock-on-active 規則執行一次
當在規則上使用ruleflow-group屬性或agenda-group屬性的時候,將lock-on-active屬性的值設置為true,可以避免因某些Fact對象被修改而使已經執行過的規則再次被激活執行。可以看出該屬性與no-loop屬性有相似之處,no-loop屬性是為了避免Fact修改或調用了insert,retract,update之類導致規則再次激活執行,這里lock-on-active屬性也是這個作用,lock-on-active是no-loop的增強版。
作用:在使用ruleflow-group屬性或agenda-group屬性的時候,默認是false,設置為true,該規則只會執行一次。
ruleflow-group 規則流分組
在使用規則流的時候要用到ruleflow-group屬性,該屬性的值為一個字符串,作用是將規則划分為一個個的組,然后在規則流當中通過使用ruleflow-group屬性的值,從而使用對應的規則。該屬性會通過流程的走向確定要執行哪一條規則。
4.1.2 LHS
定義當前規則的條件,如 when Message();判斷當前workingMemory中是否存在Message對象。
LHS部分是由一個或多個條件組成,條件又稱為pattern(匹配模式),多個pattern之間可以使用 and 或 or來進行連接,同時還可以使用小括號來確定pattern的優先級
【綁定變量名:】Object(【filed 約束】)
對於一個pattern來說"綁定變量名"是可選的,如果在當前規則的LHS部分的其他pattern要使用這個對象,那么可以通過為該對象綁定設定一個綁定變量名來實現對其的引用,對於綁定變量的命名,通常的做法是為其添加一個 "$"符號作為前綴,可以和Fact對象區分開來。
綁定變量可以用於對象上,可以用於對象屬性上,"field約束"是指當前對象里相關字段的條件限制。
以上,
第一個pattern有三個約束
1、對象類型必須是Customer;
2、Customer的age要大於20
3、Customer的gender要是male
第二個pattern有三個約束
1、對象類型必須是Order
2、Order對應的Customer必須是前面那個Customer
3、當前這個Order的price要大於1000
這兩個pattern沒有符號連接,在Drools當中沒有連接符號,默認是and,只有兩個pattern(模式)都滿足才會返回true。
4.1.3 RHS
RHS是滿足LHS條件之后進行后續處理部分的統稱,該部分包含要執行的操作的列表信息。RHS主要用於處理結果,因此不建議在此部分再進行業務判斷。
RHS的主要功能是對working memory中的數據進行insert、update、delete或modify操作,Drools提供了相應的內置方法來幫助實現這些功能。
RHS中可以寫java代碼,即當前規則條件滿足執行的操作,可以直接調用Fact對象的方法來操作應用。
4.1.4 Function函數
函數是將語義代碼放置在規則文件中的一種方式,就相當於java類中的方法一樣。使用函數的好處是可以將業務邏輯集中放置在一個地方,根據需要可以對函數進行修改。
4.2 決策表
決策表是一個”精確而緊湊的”表示條件邏輯的方式,非常適合商業級別的規則。目前決策表支持xls格式和csv格式。
4.2.1 何時使用決策表
如果規則能夠被表達為模板+數據的格式,可以考慮使用決策表。
決策表中的每一行就是對應一行數據,將產生一個規則。
4.2.2 運行決策表
首先,決策表將轉換為Drools規則語言(DRL),然后執行規則引擎需求。
4.2.3 決策表的配置
全局配置:
RuleTable部分
4.2.4 決策表例子
年齡分類規則如下:
決策表設計:
POJO對象:
測試類:
結果:
4.3 規則流
4.3.1 什么是規則流
規則流能夠控制,規則中的復雜流程,在復雜業務中,很多時候並不需要觸發所有的規則,很多時候需要觸發的規則也需要像程序一樣,符合某些邏輯,如,當X對象X 屬性等於 A 時,觸發 規則A 中的規則,當等於B時,觸發規則B中的規則,這時候用規則流就能夠很好的處理這類問題。
4.3.2 如何使用規則流
創建規則流文件如score.bpmn
指定規則文件相關屬性:
流程中指定,評分模型的規則組:
設置網關判定條件:
編輯各條網關連接線的判斷依據:
4.3.3 規則流組件
RuleTask
規則任務,可和規則文件中指定規則組關聯
ScriptTask
腳本任務,可編寫腳本完成指定任務
Gateway
按網關方向可分為聚合網關diverging gateway和分散網關converging gateway
按網關類型可分為:
u 唯一網關XOR網關
唯一網關會選擇一個順序流, 如果條件執行為true。如果多個條件 執行為true,第一個遇到的就會被使用。
u 並行網關AND網關
l 並行網關擁有一個進入順序流的和多於一個的外出順序流 叫做'並行切分或 'AND-split'。所有外出順序流都會 被並行使用。
l 並行網關擁有多個進入順序流和一個外出順序流 叫做'並行歸並'或 AND-join。所有進入順序流需要 到達這個並行歸並,在外向順序流使用之前。
u 包含網關OR網關
包含網關先后執行為true的分支。
5. 開發環境搭建
5.1安裝Drools插件
5.1.1 下載Drools插件
可以去工具集中下載
5.1.2 安裝Drools插件
Help->Install New Software
重啟Eclipse,如果Preferences中出現Drools則說明插件安裝成功。
5.2安裝Drools Runtimes(Drools運行時)
5.2.1下載Drools Engine
5.2.2在Eclipse中指定Drools Runtimes
Windows->Preferences->Drools->Installed Drools Runtimes
可以新建Drools項目
6. Drools代碼編寫注意事項
6.1規則流部分
- 每個flow需要有至少一個進入點(Start Event)和一個退出點(End Event)
- 網關按方向有diverge(分離網關)和converge(匯聚網關),需要合理使用
- 網關按類型有唯一網關(XOR)、並行網關(AND)和包含網關(OR),需要合理使用
- 唯一網關(XOR)作為分離網關,會選擇外出的多個順序流中的一個
- 並行網關(AND)作為分離網關,擁有一個進入順序流和多個外出順序流,叫做“並行切分(AND-split)”,所有的外出順序流都會被執行
- 並行網關(AND)作為匯聚網關,擁有多個進入順序流和一個外出順序流,叫做“並行歸並(AND-join)”,所有進入順序流都需要到達並行網關,才會執行外出順序流
- 包含網關(OR)基本行為和唯一網關類似,區別在於,滿足條件的外出順序流都會被執行
- Id唯一的代表一個flow元素,不能重復
- 界面創建flow元素后自動生成的MetaData的UniqueId需要檢查不能以數字開頭,否則調用規則流引擎時會報錯
- 如果規則流需要引用WorkingMemory中的Fact對象,需要在規則流的Properties中定義Variables,此變量需要在Java代碼調用規則流時作為參數傳入
6.2決策表部分
- 合理設置RuleSet名稱,確保不重復
- 決策表編寫需注意格式,如CONDINTION下第四行是第一條數據(規則)配置行
- CONDITION和ACTION中都可以使用匹配Fact變量的成員函數
- ACTION中更新WorkingMemory中Fact對象后需考慮是否規則是否會反復觸發導致死循環
- 如果想規則始終進入,可使用eval(true)
- CONDITION中如果使用可能會變化的Fact對象的Map中的value,需要使用原生的get方法
- CONDITION中使用占位符匹配規則時,如果是字符串需要用雙引號引起來
6.3 Java代碼部分
- 需要使用session的insert方法將需要處理的對象插入到WorkingMemory
- 如果規則流需要使用Fact對象,需要在調用規則流的時候將Fact對象放入map中,作為參數傳入
- 啟用規則流后,還需要調用session的fireAllRules才會執行匹配的規則
有狀態session使用后必須顯式調用dispose方法來釋放,避免內存。
可以參考代碼:https://github.com/renfeihn/drools7-demo