非全自研可視化表達引擎-RuleLinK


 說在前面

工作中經常會遇到這樣的場景:

  •  幫忙把小貝拉門店 商品金額在5w以內,產康訂單最多95折。

  • 幫忙把聖貝拉門店 開業時間在6個月內,折扣低於7折要發起審批

  • 幫忙把寧波太平洋店設置獨立合同模板

  • 幫忙把節假日成本變成1000

  • ...

有些三五月變一次,有些一月變一次,有些一週變幾次,每次修改都是去巴拉代碼,不勝其煩。

系統中的規則分散在各各PRD,有些人腦袋裏也存了一份,但是隨着人員的迭代,需求的迭代,最新永遠散落在各初的代碼中。這會帶來諸多問題:

  • 學習成本高

  • 維護成本高

  • 開發成本高

  • 測試成本高

於是有了創建一個規則的管理與運營平臺,以及支撐規則的解釋與執行的框架的想法。用以消除樣板式代碼,解放生產力,讓規則的變化變得的簡單。

 

RuleLink的定位

RuleLink是一款可視化表達式引擎。致力於解決業務開發過程中,規則變化成本高, 規則管理分散,維護成本高,學習成本高,大量樣板式代碼等一系列的問題。

RuleLink名稱的由來

用規則連接業務邏輯,讓研發專注業務邏輯,讓規則的配置變得的簡單。

RuleLink 是基於 Aviatorscript  實現 規則的解析,基於開源項目 "rule-engine-builder-ui"實現表達式生成與渲染。(我只是代碼搬運工,所以取名非全自研)

基於當下我們的業務規則量,RuleLink並未使用性能更好的 Rete算法,而是使用了傳統的模式匹配。

 

爲什麼要建RuleLink

除了前面介紹的之外,另一個原因則是想彌補一下上一份的工作中的一點點小遺憾。

可視化表達式引擎的建設有兩個難點:

  1. 表達式的解釋與執行

  2. 表達式的生成與解析

第1點相對簡單,於是在1月份時,搗鼓了一些基礎代碼,做了一些嘗試,當時還因爲部署Drools 的workbench 搞得停服20分鐘,拿了人生第一C績效。

到4月份接到這樣一個需求,某業務線不同訂單訂單需要接不同的聚合支付賬號。因爲原來就換過一次了,爲了支持快速切換,我們開始在Q1的代碼基礎做了一些調整,開始有了RuleLink的雛形。後來又陸續接入了一些場景:

  • 新業務支付支持不同主體

  • 訂單折扣配置

  • ...

更多的場景的接入,讓可視化配置的需求變得比較必要。於是開始正式着手構建RuleLink,解決這一類的問題。

整體結構

目前主要使用Aviatorscript 解析表達式,並支持SpE

 

這是從其他複製的一張圖(忘記出處),因爲和自己的場景幾乎一模一樣,就直接引用了。

 

存儲模型

 

Rule_Scene 規則場景

定義場景,目前場景是固定,現在只支持支付,未來有新場景再加入

RuleFactObj 事實對象

RuleFactObjfield 事實對象字段

定義場景下的事實字段,主要用於將來前端可可視化操作。

RuleBase 規則庫

定義規則,目前只支持表達式(未來考慮支持 特定腳本,比如groovy),目前只是定義規則命中返回 簡單或者複雜數據類型,未來可以考慮執行某個運行(action)。

 

 

日期格式處理

日期格式原來的實現是SpEL的方式,這會引發一些問題,所以修改了其源碼並重新編譯生成支持Aviatorscript 支持的自定義函數方式

 1 /**
 2  * @Author: jijunjian
 3  * @CreateTime: 2023-08-25  17:52
 4  * @Description: 自定義函數初始化
 5  */
 6 @Component
 7 @Slf4j
 8 public class CustomFunctionInitializer implements ApplicationContextAware {
 9 
10     @Override
11     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
12         log.info("初始化自定義函數");
13         AviatorEvaluator.addFunction(new StringToTimestamp());
14         log.info("初始化自定義函數完成");
15     }
16 
17     /**
18      * 自定義函數
19      * 時間字符串轉時間戳
20      */
21     class StringToTimestamp extends AbstractFunction {
22         @Override
23         public AviatorObject call(Map<String, Object> env,
24                                   AviatorObject arg1, AviatorObject arg2) {
25             String timeString = FunctionUtils.getStringValue(arg1, env);
26             String format = FunctionUtils.getStringValue(arg2, env);
27             // 10位時間戳
28             long timeStamp = DateUtil.parse(timeString, format).getTime()/1000;
29             return  AviatorLong.valueOf(timeStamp);
30         }
31 
32         @Override
33         public String getName() {
34             return "string_to_timestamp";
35         }
36     }
37 }

 

 

表達式解析

使用Aviatorscript解析比較簡單,直接上代碼

 1   /**
 2      * 根據表達式執行
 3      * @author: jijunjian
 4      * @param factObj
 5      * @param expression
 6      * @return
 7      */
 8     @Override
 9     public boolean fire(Object factObj, String expression){
10         Map<String,Object> fact = new HashMap<>();
11         fact.put("data",factObj);
12         // 對於有字符串的表達式,需要先編譯(並緩存,減少生成的臨時類)
13         Expression compiledExpression = AviatorEvaluator.compile(expression,true);
14         log.info("開始執行表達式:{}, fact:{}", expression, JSONUtil.toJsonStr(factObj));
15         Boolean flag = (Boolean) compiledExpression.execute(fact);
16         log.info("開始執行表達式:{}, fact:{}, result:{}", expression, JSONUtil.toJsonStr(factObj), flag);
17 
18         return flag;
19     }
20 點擊並拖拽以移動

 

交互界面

 

 

寫在最後

雖然現在的版本距離真正讓運營同學能直接用起來了,可能還有一定的距離。比如各種枚舉支持選項,門店等動態數據支持選項,返回結果支持動態渲染和選擇等都還不不具備。但是1.0版本比原來硬編碼,甚至nacos配置已經強上許多了。畢竟還是和兩個小夥伴擠壓業餘時間開發,實爲不易,於是來一次簡單的聚餐,於是我們預定了未來每次大的版本升級,都來一次

 

 

 

上一份工作時,就有要構建一個簡單易用的規則引擎,一直沒能實現,有些許遺憾,今天算是給補上了。

微信:jijunjian

成爲一名優秀的程序員!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章