規則引擎深度對比,LiteFlow vs Drools!

2022-10-18 12:01:06

前言

Drools是一款老牌的java規則引擎框架,早在十幾年前,我剛工作的時候,曾在一家第三方支付企業工作。在核心的支付路由層面我記得就是用Drools來做的。

難能可貴的是,Drools這個專案在十幾年後還依舊保持著開源和更新。

https://github.com/kiegroup/drools

而LiteFlow也是一款java規則引擎,於2020年開源。經過2年的迭代,現在功能和特性也非常棒,很適合用在高複雜度的核心業務上,同時又能保持業務的靈活性。

https://gitee.com/dromara/liteFlow

這篇文章我們就來深入比較下這兩款框架,都適合用在什麼樣的場景,有什麼異同點,以及在相同的場景下表現力如何。

(其中Drools基於7.6.0版本,LiteFlow基於2.9.0版本)

雖然題主就是開源專案LiteFlow的作者,但是我這幾天也深入瞭解了下Drools,儘量從很客觀的角度嘗試去分析。很多比對的結果都是基於實際使用後的感受。不過題主難免會帶有一些主觀的心理以及瞭解的片面性,尤其是Drools現在已經更新到了8.X,說實話並沒有使用過。所以說的不正確的地方也請指正。

規則引擎的定義

首先我想明確下規則引擎的定義,因為很多小夥伴容易把規則引擎和流程引擎的概念混在一起。

規則引擎通常是嵌入在應用程式元件中的,實現了將業務決策從應用程式程式碼中分離出來,並使用預定義的語意模組編寫業務決策。接受資料輸入,解釋業務規則,並根據業務規則做出業務決策。

簡單來說就是,規則引擎主要解決易變邏輯和業務耦合的問題,規則驅動邏輯。以前專案內寫死在程式碼裡的邏輯用規則引擎可以提出來,隨時熱變更。

而流程引擎實現了將多個業務參與者之間按照某種預定義的規則進行流轉,通常需要涉及到角色資訊。

簡單來說就是,流程引擎主要解決業務在不同角色之間的流轉問題,如請假流程,審批流程,往往要經過多個角色。規則驅動角色流轉。

兩款框架的異同點

Drools和LiteFlow都是優秀的開源框架,都能把業務中的邏輯給剝離出來。並且擁有自己表示式語法。

但是有所區別的是,Drools強調邏輯的片段規則化,你可以把核心易變部分寫成一個規則檔案,等同於原先寫在java裡的程式碼現在搬遷到了規則檔案。規則檔案裡的程式碼全都是可以熱變更的。

而LiteFlow是基於元件式的思想設計的,更強調元件的規則化,覆蓋範圍是整個業務。編排的最小單位是元件,規則檔案用來串聯元件間的流轉。同時LiteFlow也支援片段式的程式碼規則化,因為LiteFlow也支援業務邏輯的指令碼化。規則支援熱變更。

所以評判一個規則引擎是否合格的主要因素有:

  1. 有沒有靈活的規則表示式來支援
  2. 規則和Java之間能否非常方便的聯動
  3. API呼叫是否方便,和各種場景系統的整合如何
  4. 侵入性耦合比較
  5. 規則的學習成本,是否容易上手
  6. 規則表示式是否有語言外掛
  7. 規則能否和業務鬆耦合,儲存於其他地方
  8. 規則的變更能否實時改變邏輯
  9. 是否有介面形態來支援非技術人員的使用
  10. 框架的效能表現

下面就從這幾個方面來細細比較兩款框架的表現力

規則表示式

Drools的規則表示式為Java量身客製化的基於Charles Forgy的RETE演演算法的規則引擎的實現。

Drools的規則表示式貼近自然程式語言,擁有自己的擴充套件名檔案drl,語法支援全,基本上自然程式語言有的語法drl全有。所以,完全可以把java的邏輯寫在drl檔案中。

來看下drl檔案的大體樣子:

可以看到,Drools定義規則的方式是一個規則一段,有明確的when...then,表示當滿足什麼條件時,做什麼。在觸發規則時候,會自動判斷該去執行哪一段rule,如果滿足多個條件,是可以觸發多個規則的then的。

LiteFlow編排表示式簡單易懂,底層用EL表示式語言套件裝而成。用於元件的流轉,支援非同步,選擇,條件,迴圈,巢狀等一些場景。

元件層面不僅可以是java元件,還可以用指令碼語言來編寫,目前支援了Groovy和QLExpress兩種指令碼語言。所有能用java實現的,用指令碼語言都可以做到。

LiteFlow的規則檔案大體長這個樣子:

上述LiteFlow的編排表示式中,所表達的是下面一個邏輯流:

LiteFlow編排表示式支援THEN(同步),WHEN(非同步),SWITCH(選擇),IF(條件),FOR(次數迴圈),WHILE(條件迴圈)等大表示式,每個表示式又有許多擴充套件關鍵字可供選用。

指令碼元件支援的Groovy基本和java語法差不多,Groovy語言支援的一切你均可使用。甚至可以在Groovy語法中額外定義類和方法。

結論

總的來說,兩款框架都能用指令碼來定義邏輯片段,在定義邏輯片段層面,Drools使用的是自研語法,LiteFlow使用的是外掛式的Groovy,其實個人覺得Groovy更接近java語法,你甚至於可以在其中定義類和方法。Drools在高階應用中,也可以用規則定義方法,但是我覺得並不那麼自然。

LiteFlow最大的特點是除了定義邏輯片段外,還可以進行全域性元件的編排。而這正是LiteFlow稱之為編排式規則引擎的由來。使用簡單的編排語法可以設計出複雜的邏輯流。支援java和指令碼混編。

和Java的資料交換

在Drools的規則中,你可以通過import關鍵字來引入java的一些類包類進行呼叫。

在LiteFlow的指令碼元件中,Groovy也可以通過import 來引入java的任何包來呼叫。

Drools中,可以直接參照到fact物件。

LiteFlow中,可以直接參照到context物件,context上下文貫穿整個編排鏈路。

LiteFlow中,通過@ScriptBean註解,你甚至可以把spring上下文中的bean引入進來直接呼叫。利用這個特性,甚至於可以在指令碼中呼叫rpc,呼叫資料庫dao物件取資料。這個在Drools裡面雖然也可以做到,但是要麻煩的多。

結論

基本都能滿足和java的資料交換需求,但是LiteFlow在場景上支援的顯然更加多一點。

API以及整合

在API呼叫層面,Drools需要去定義KieContainer,KBase,KSession一系列物件。LiteFlow框架只需要使用到LiteFlowExecutor物件。

Drools支援了程式設計式接入,但是在springboot中需要自己寫很多設定類來去整合。

LiteFlow不僅支援了程式設計式接入,在springboot環境下更是提供了自動裝配的starer接入方式,連定義LiteFlowExecutor都不需要,直接從上下文中就可以拿到自動裝配後的物件進行呼叫。

結論

LiteFlow api更加簡單,同Springboot整合度更加高。

侵入性耦合比較

Drools需要在java程式碼裡需要用到規則的地方用KSession物件去匹配規則進行呼叫。規則和java是分離的。在呼叫層面耦合了KSession呼叫物件。

LiteFlow的規則和java也是分離的,但是LiteFlow多了元件這一概念,所以在元件層面是需要繼承的,但是同時也提供宣告式元件的選擇,使用宣告式的方式耦合相對要減少一些。在呼叫層面也需要去呼叫LiteFlowExecutor物件。

結論

在耦合度上面,由於LiteFlow提供編排特性,API耦合度相對稍高一些。Drools耦合少一些。

規則的學習成本

Drools的規則學習成本挺高的。由於是自研的規則語法,需要一個很全面的熟悉過程。而且檔案全英文。

LiteFlow的編排規則極其簡單,如果你不使用指令碼元件的話,基本上10分鐘即可上手。就算使用了groovy指令碼,由於groovy非常類似於java,學習成本也非常少。況且有大量的學習資料可以參閱。

LiteFlow的檔案中英文齊全,還有良好的中文社群可以答疑解惑。

結論

在規則學習成本上,Drools的規則學習曲線比LiteFlow高出不止一丁點。

是否有語言外掛

Drools在Eclipse和IDEA上均有外掛來做語法的高亮,預檢查和提示。

LiteFlow在IDEA上有外掛來做高亮,預檢查和提示。Eclipse上沒有。

結論

考慮到使用eclipse的人幾乎很少了,基本上2款規則引擎在語言外掛上都做到了。

規則的儲存

Drools的規則理論上支援你的規則存於任何地方,但這一切都需要你手動去額外完成。自己去存,自己去取。

Drools還有款workbeanch的外掛,可以將規則存於workbeanch中。只有這個是不需要自己存取的。

LiteFlow除了本地規則以外,原生支援將規則儲存於任何標準SQL的資料庫,還原生支援了Nacos,Etcd,zookeeper等註冊中心。只需要設定一下即可。除此之外,還提供了擴充套件介面,方便你自己擴充套件成任意的儲存點。

結論

LiteFlow的規則儲存支援比Drools豐富的多。

規則的變更能否實時改變邏輯

Drools熱重新整理規則的方式現在看起來有點傻,它的規則是通過生成jar的方式。然後系統遠端動態讀取jar包來完成規則重新整理的。

而且一定得通過workbench的方式進行規則的熱變更。

LiteFlow在這個層面做的高階很多。如果你是用Nacos,Etcd,zookeeper等方式儲存,不用做任何事,改變即自動重新整理。如果你是SQL資料庫儲存,或者本地儲存。在改變規則之後,需要呼叫LiteFlow框架提供的一個API進行熱變更。2種方式均可熱更新。並且在高並行情況下是平滑的。

結論

LiteFlow在熱更新設計層面比Drools先進很多。

是否有介面形態來支援

Drools有workbench,workbench是一個獨立的外掛包,提供了web介面編寫規則以及fact物件。並提供了檢查和部署的能力。但因為Drools主要關心邏輯片段,並不需要提供編排層面的拖拽UI功能,只是提供了在介面上編寫規則的能力。

LiteFlow並沒有介面形態。目前只能通過第三方的Nacos,Etcd提供的介面來輔助完成介面的規則修改。

結論

Drools在UI形態生態上領先LiteFlow一截。

框架的效能表現

這裡用Drools和LiteFlow實現了同樣的一段邏輯Demo。

根據訂單金額來加積分的Demo案例。

案例邏輯很簡單,根據訂單的金額來動態判斷該加多少積分:

小於100元,不加積分。

100到500元,加100積分。

500到1000元,加500積分。

1000元以上,加1000積分。

其中Drools的規則如下:

package rules;

import com.example.droolsdemo.entity.Order;

rule "score_1"
when
    $order:Order(amount<100)
then
    $order.setScore(0);
    System.out.println("觸發了規則1");
end

rule "score_2"
when
    $order:Order(amount>=100 && amount < 500)
then
    $order.setScore(100);
    System.out.println("觸發了規則2");
end

rule "score_3"
when
    $order:Order(amount>=500 && amount < 1000)
then
    $order.setScore(500);
    System.out.println("觸發了規則3");
end

rule "score_4"
when
    $order:Order(amount>=1000)
then
    $order.setScore(1000);
    System.out.println("觸發了規則4");
end

其中等價的LiteFlow規則如下:

<?xml version="1.0" encoding="UTF-8"?>
<flow>
    <nodes>
        <node id="w" type="switch_script">
            <![CDATA[
                def amount = defaultContext.getData("order").getAmount();

                if (amount < 100){
                    return "a";
                }else if(amount >= 100 && amount < 500){
                    return "b";
                }else if(amount >= 500 && amount < 1000){
                    return "c";
                }else{
                    return "d";
                }
            ]]>
        </node>

        <node id="a" type="script">
            <![CDATA[
                def order = defaultContext.getData("order");
                order.setScore(0);
                println("執行規則a");
            ]]>
        </node>

        <node id="b" type="script">
            <![CDATA[
                def order = defaultContext.getData("order");
                order.setScore(100);
                println("執行規則b");
            ]]>
        </node>

        <node id="c" type="script">
            <![CDATA[
                def order = defaultContext.getData("order");
                order.setScore(500);
                println("執行規則c");
            ]]>
        </node>

        <node id="d" type="script">
            <![CDATA[
                def order = defaultContext.getData("order");
                order.setScore(1000);
                println("執行規則d");
            ]]>
        </node>
    </nodes>

    <chain name="chain1">
        SWITCH(w).TO(a, b, c, d);
    </chain>
</flow>

兩款框架都全用指令碼來寫的情況下,測試的過程中,去除所有的列印紀錄檔,執行10w次,得到的結果如下:

Drools 執行10w次,耗時0.7秒

LiteFlow全指令碼元件執行10w次,耗時3.6秒

由於LiteFlow在全指令碼元件的情況下,需要做指令碼的執行和編排指令碼的執行,所以花費的時間更長。

如果LiteFlow把元件更換成java,再進行執行,得到的結果如下:

LiteFlow 全Java元件執行10w次,耗時0.5秒

結論

如果LiteFlow採用全指令碼的方式執行,耗時會比Drools更長。如果採用全java元件的方式執行,其效能能超越Drools一點。

所以對於LiteFlow而言,如果你希望更高的效能,則採用java元件,如果你希望更高的靈活性,則採用指令碼元件。

其實在實際業務中,把容易更改的邏輯抽出來寫成指令碼元件,採用java+指令碼混編的方式,是更為推薦的做法。

結語

為什麼會拿Drools來作為比較,其一在題主心中,Drools一直是規則引擎界的標杆,drools有很多理念非常值得學習。其二也是因為題主也只熟悉Drools,其他的框架沒有很好的使用過的緣故。

但是綜合來看,作為國產規則引擎後起之秀LiteFlow顯然在設計理念,支援度方面是要優於Drools的。編排式規則引擎作為規則引擎的一個新的方向,也會一直探索下去的。希望大家能多多支援這款國產的規則引擎。在編排方向,LiteFlow除了文中所提到的一些特性以外,還有很多其他各種各樣的探索性的玩法和高階特性。是一款很值得深挖的框架。

官網地址: https://liteflow.yomahub.com/