轉轉圖書對基于Drools引擎的DMN實踐
1 背景介紹
1.1 DMN是什么
DMN全稱Decision Model and Notation(決策模型和符號、決策模型和表示法),是一種用于表示業務決策和規則的規范,旨在幫助參與決策的人都能簡單快速理解決策過程。
DMN logo
DMN是由OMG(Object Management Group,對象管理組織)管理的一種規范,該組織下比較知名的還有UML等。
DMN示例
如圖所示,DMN的表現形式近似于流程圖,通過可視化來更直觀地體現出流轉和處理的過程,而各個節點使用附帶邏輯的表格來表現該節點處理數據的方式。
由于DMN是一個規范,所以在應用上,主要依靠各個DMN工具供應商提供具體實現,近似于SQL語句和MySQL、Oracle的關系。
1.2 為什么要用DMN
應用DMN主要目的是為了解決轉轉圖書項目定價邏輯過于復雜的問題。
在引入DMN之前,圖書項目使用java代碼實現產品人員提供的定價邏輯。但是隨著邏輯越來越復雜,定價邏輯的代碼也越來越難以閱讀和維護。同時,將產品人員的邏輯翻譯成代碼是一個單向過程,只有程序員能理解實現過程,產品人員只能通過結果反推是否正確合理,在邏輯變得復雜之后,很難快速發現問題。
復雜規則的一部分,區間和系數
而通過DMN,可以可視化的表示出邏輯過程,產品人員也可以直觀的看到是否符合預期,還能夠進行編輯,從而解決了只有程序員掌握實現而過于依賴程序員的問題,也能減少表述交流的過程產生理解偏差進而導致的錯誤。
使用DMN實現的規則表現
1.3 什么是Drools
Drools是用java語言編寫的開源規則引擎,是DMN規范的一種實現。舉例來說DMN規范就是接口,Drools就是實現了接口的其中一個工具類。
Drools logo
Drools屬于Kie開源社區,而kie社區由Red Hat贊助,有比較高的社區活躍度。
Kie旗下的產品
1.4 為什么選擇Drools
Drools引擎是基于java實現的,這使得不需要為Drools單獨部署運行環境,運維成本是0,十分友好。
其次,DMN規范規定實現其規范的軟件必須滿足三級遞增的符合性級別,三級最高,一級最低,滿足三級級別時必須同時滿足一級和二級級別。而Drools引擎對DMN規范的支持屬于三級級別,功能完善。
Drools引擎對DMN各個版本提供支持
同時,Drools引擎社區活躍度高,同時提供了完整的工作組件,可靠性高。
2 Drools引擎應用
以下內容基于7.61.0版本,與8.x及以上的版本存在差異
2.1 官方推薦的最直接的應用方式
首先需要部署運行一個Kie Server用來執行DMN規則,然后部署Drools官方提供的基于Web的工作站軟件用來編輯、測試、發布DMN規則。
Drools提供的工作站軟件
當部署完成DMN規則之后,可以通過Kie Server REST API來運行規則獲取結果,即通過Http的方式請求Kie Server提供的接口。
2.2 轉轉圖書的限制
首先說圖書項目并沒有直接按照官方推薦的做法部署應用,原因有幾個方面:
第一點,轉轉內部項目部署運行有自有的框架體系,而Kie Server是基于JBoss運行的服務器軟件,在現有體系中部署一個外部項目需要額外的運維成本。
其次,Kie Server作為一個第三方工具,當出現問題時僅靠圖書項目的人員難以解決,而等待社區反饋對于轉轉圖書這種線上的商業項目難以接收。所以為了盡可能的減少出現問題的概率,節約人力,圖書項目傾向于盡可能少的引入外部依賴。
基于以上原因,圖書項目在應用Drools的時候選擇了另外一種方式。
3 脫離Kie Server的Drools引擎實踐
脫離開Kie Server,自然就沒有了REST API的http接口,同時也失去了官方工作站的支持,但是相對的,Drools提供了一些對這種場景下的支持。
3.1 在線編輯DMN規則
在Kie旗下有另一款名為Kogito的產品,是一個提供在線編輯BPMN和DMN的服務器軟件,同時其中有一個all-in-one的js文件,實現了在線編輯DMN的全部功能。
js提供的接口
圖書項目基于這個js文件進行了包裝,增加易用性,使得DMN編輯功能融入到現有的工作后臺之中。
包裝頁面,下方是js提供的功能區
同時增加編輯記錄列表用來便于管理和回滾。
編輯列表
js工具編輯后的DMN規則內容是xml格式的字符串,可以使用js提供的getContent()接口導出。
3.2 使用Drools引擎執行DMN規則
沒有了Kie Server的支持之后,需要通過代碼的方式運行Drools引擎。
首先在項目中引入Drools引擎的組件
引入Drools引擎組件
然后在項目中創建Drools引擎的引用并執行。
由于Kie容器使用maven作為讀取DMN配置的手段,所以要求DMN規則內容需要打包到Kie容器能夠識別的jar包里,并且部署到項目可訪問的maven環境中。
3.3 完善處理流程
按照上述流程就可以實現脫離Kie server運行Drools引擎。但是距離落地上線還有一小點距離。
官方的工作站可以提供DMN規則上線前的驗證和測試功能,但是all-in-one的js文件沒有,所以為了保證準確性和穩定性,需要額外實現DMN規則的驗證動作。
這里可以使用KieContainer提供的verify接口來觸發Drools引擎的驗證動作并獲取結果。
將驗證流程放到保存DMN規則的時候,就可以快速發現是否存在編寫錯誤。
3.4 DLC - 脫離maven環境運行Drools引擎
前面說了Drools引擎需要使用maven獲取運行的DMNjar文件,但是在實際應用中,線上環境不一定會部署maven。例如圖書項目需要在公司內部公用的spark集群上運行Drools,但是spark集群上沒有部署maven環境,按照上述流程運行就會報錯。
所以需要一個能夠使Drools脫離maven環境的手段。
通過分析源碼可以發現,在創建KieContainer的時候,會使用KieServices.getRepository()方法獲取數據源,并通過maven坐標查找其中的KieModule。所以需要做的就是直接我們需要的jar寫入KieServices的數據源中:
如上就可以實現在沒有maven環境的時候仍然能夠讓KieServices獲取到我們需要DMN規則。
但是僅僅如此還不夠,在創建KieContainer的時候,內部會生成一個KieProject實例,KieProject在實例化的過程中會默認生成一個基于maven的MavenClassLoaderResolver用于查找jar,而缺少maven環境的情況下在生成MavenClassLoaderResolver的時候也會報錯。為此,我們需要一個取代MavenClassLoaderResolver的辦法。
通過分析Drools源碼可以發現,在源碼中,某些時候會使用ProjectClassLoader.findParentClassLoader()來獲取基于當前運行環境的ClassLoader。
所以,需要在創建KieContainer的時候使用ProjectClassLoader.findParentClassLoader()生成的ClassLoader來取代默認的MavenClassLoaderResolver:
如此一來,就可以完全脫離maven環境來使用Drools引擎了,而Drools引擎也可以融入到任意的java項目之中,部署到任意的java環境之下。
4 總結
DMN在實際應用中有比較明顯的優點:
- 可以在不修改代碼的情況下更新邏輯,減少上線過程中可能產生的問題
- 可以通過一套代碼實現各種場景的需求
- 流程可視化,便于理解邏輯的運行
- 能夠讓一部分不熟悉程序代碼的人員也可以參與編輯,避免流程全部只由程序員了解
但是相對的也存在一些弊端或者表現不夠好的地方:
- 流程復雜時圖形化表現比較雜亂,尤其是多個決策節點依賴相同的上游時
- 編輯規則時還是會有一部分場景條件需要寫代碼才能實現,例如列表包含(可以使用DMN專用代碼FEEL實現,或者引用已經實現的java方法實現)
- 運行規則的時間相較于使用java代碼實現要慢,因為中間涉及規則文件解析和各個節點的計算,而java代碼可以更直接的實現
復雜規則示例
總體而言,DMN主要應用在向程序員以外的人員提供決策管理的能力,以求更準確地反映目的,從程序員的角度講可能和寫ifelse沒什么不一樣,但是其他角色的參與人員可以通過較低的學習成本來上手實現規則,能夠減少溝通成本和不同人的理解差異產生的不符合預期的結果。
5 參考資料
Drools官方網站:https://www.drools.org/
Drools官方的DMN教學:https://www.drools.org/learn/dmn.html
Drools官方的15分鐘簡易教學:https://learn-dmn-in-15-minutes.com/learn/introduction
Kogito在線編輯網頁:https://sandbox.kie.org/#/
Kie官方網站:https://www.kie.org/
OMG官方網站的DMN頁:https://www.omg.org/dmn/
作者簡介
項贏,轉轉資深java工程師。長期服務于轉轉圖書項目。