基於drools語法實現衍生變量功能的設計

基於drools語法實現衍生變量功能的設計

概述

在現有的設計中,我們使用頁面拖拉選擇用戶變量並編輯條件、按照drools的語法生成規則腳本,生成的結果類似於下邊這樣:

import java.util.*;

rule "rule01"
when
    $m:Map()
    eval(true) && Map(this["A"]>=1000) && Map(this["B"]==5)
then
    $m.put("RESULT",true);
end

如上規則中的AB都是用戶寬表中變量,需要提前計算好並存入數據庫中,跑規則之前通過用戶證件號去查詢獲取,塞進Map中再跑規則。

但是,在實際業務中,還存在很多用戶變量,他其實可以根據用戶現有的一些變量推算出來(比如 C = MIN(A,5) * B / 5。假如我們不做修改,那麼,還是需要寫死代碼、寫sparkSQL去把每個用戶的C算出來寫進數據庫。每次增加類似變量,都需要做一次代碼開發、測試、投產流程。

因此,我們需要這麼一種衍生變量功能,它能夠支持我們對用戶現有變量做組合加工,從而生成一個新的變量,這個變量的使用——包括拖拉選擇、條件設置、腳本生成、頁面查詢——都應該跟現有簡單變量一致,但它的來源不是其他業務代碼的計算,而是我們系統實時對現有變量的組合加工而成。

思路

還是考慮上邊的例子,假如我們通過頁面編輯客戶達標條件爲a >= 1000 && B == 5 && C == 5.0,那麼,我們最終期望生成的代碼應該是這樣:

import java.util.*;

rule "rule01"
when
    $m:Map()
    eval(true) && Map(this["A"]>=1000) && Map(this["B"]==5) && Map(this["C"] == 5.0)
then
    $m.put("RESULT",true);
end

由於變量CAB加工而成,即C = MIN(A,5) * B / 5,因此,可以把上邊的規則腳本修改爲:

import java.util.*;
global me.ltang.DroolsFunctionNumber num;

rule "rule01"
when
    $m:Map()
    eval(true) && Map(this["A"]>=1000) && Map(this["B"]==5) && eval(num.MIN($m.get("A"), 5) * Integer.parseInt(String.valueOf($m.get("B"))) / 5.0 == 5.0)
then
    $m.put("RESULT",true);
end

上邊的腳本中,我把取最大值、最小值、取小數位等drools不方便表達的運算操作封裝在DroolsFunctionNumber類中(如下示),可以直接在腳本中調用。但還有個問題就是,由於我們的map是Map<String,Object>,因此get到的Object不能直接用於數學運算(±*/等),還需要額外的代碼(Integer.parseInt(String.valueOf(...)))將Object轉爲數值類型。因此,我也在考慮是否將加減乘除取餘等數學操作,也封裝在DroolsFunctionNumber中,這樣,生成的腳本會簡短清晰一些。不過,不管怎樣,這並不影響大局。

public class DroolsFunctionNumber {
    /**
     * 獲取最小值
     *
     * @param ns
     * @return
     */
    public static double MIN(Object... ns) {
        double min = Double.MAX_VALUE;
        for (Object n : ns) {
            if (n == null) {
                continue;
            }
            double dn = Double.parseDouble(String.valueOf(n));
            if (dn < min) {
                min = dn;
            }
        }
        return min;
    }

    public static Double SCALE(Object n, Integer scale, String rondingMode) {
    ...

因此,除了現有的規則編輯功能外,我們需要提供新的衍生變量編輯功能,讓用戶可以在頁面編輯 C = ...,然後,在通過規則配置生成腳本時,把C對應的計算邏輯替換到腳本中C的位置。

優化

在上邊的設計中,C的值是每次執行規則時由drools算出來,這樣有兩個缺點:

  1. 假如在一個活動規則裏面,C的值被多次用到,那麼就需要多次計算C的值;
  2. 假如在java代碼裏面需要用到這個C的值,我們還無法從用戶變量(Map<String,Object>)中獲取,還需要額外的drools腳本將這個值計算出來

因此,考慮這麼一種優化:在真正執行業務邏輯的規則代碼之前,執行變量C的計算邏輯,將算好的值put進用戶變量map,後續的規則和代碼中可以直接使用。優化後的規則腳本如下所示:

import java.util.*;
global me.ltang.DroolsFunctionNumber num;

rule "value_C"
salience 1
no-loop true
when
    $m:Map(this["A"] != null, this["B"] != null)
then
    $m.put("C", num.MIN($m.get("A"), 5) * Integer.parseInt(String.valueOf($m.get("B"))) / 5.0);
    update($m)
end

rule "rule01"
when
    $m:Map()
    eval(true) && Map(this["A"]>=1000) && Map(this["B"]==5) && Map(this["C"] == 5.0)
then
    $m.put("RESULT",true);
end

如上,我們在value_C規則中,將C的值計算出來了,後邊的業務規則rule01,只需要按現有的模式——將它當成一個普通的變量——使用即可,不管業務代碼裏面使用了幾次C,都只需要計算一次;此外,執行完這個規則腳本後,後續的代碼中想使用C的值,直接map.get("C")即可。

當然,這樣有一個缺點,假如業務代碼中A/B其實不滿足條件,那麼C其實是不需要計算的,但這個方案中,不管A和B滿不滿足條件,我們都先把C算出來了,這也是一個成本。不過,兩權相利取其重,還是使用優化方案比較好。

想要更多

在上邊的設計中,我們實現了對數值類型的衍生變量的支持,但其實,有時候我們不滿足於數值類型。舉個簡化的例子,用戶M,如果他的屬性A爲true, 屬性B爲1000,那麼我認爲這個用戶有個衍生的屬性C的值爲"高附加值用戶",否則爲"低附加值用戶"。那麼,我的規則可以這麼寫:

import java.util.*;

rule "value_C_01"
salience 1
no-loop true
when
    $m:Map(this["A"] == true && this["B"] == 1000)
then
    $m.put("C", "高附加值用戶");
    update($m)
end

rule "value_C_02"
salience 1
no-loop true
when
    $m:Map(this["A"] != true || this["B"] != 1000)
then
    $m.put("C", "低附加值用戶");
    update($m)
end

不過,這裏需要注意的是,我們如何對用戶(業務)提供編輯能力?理論上,對於數值類型的C,用戶只需要編輯C的取值邏輯爲num.MIN($m.get("A"), 5) * Integer.parseInt(String.valueOf($m.get("B"))) / 5.0即可,其他的語法,我們可以自動替用戶補全。但是對於最後例子中則不適用了,除非,我們把規則腳本改寫成下面這樣子:

rule "value_C"
salience 1
no-loop true
when
    $m:Map()
then
    $m.put("C", String.valueOf($m.get("A")).equals("true") && String.valueOf($m.get("B")).equals("5000") ? "高附加值用戶":"低附加值用戶");
    update($m)
end

這樣,業務僅僅需要編輯C = String.valueOf($m.get("A")).equals("true") && String.valueOf($m.get("B")).equals("5000") ? "高附加值用戶":"低附加值用戶"。不過,這樣的話,除了同樣噁心的類型轉換外,還需要額外加個判斷——需不需要對AB做判空處理, 避免因爲A/B值爲null導致計算異常。

總的來說,我是傾向於最後這種方案,可以適用於大部分的衍生變量計算,至少能滿足現有需求了。

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