1. 程式人生 > >Drools 規則引擎

Drools 規則引擎

 

https://blog.csdn.net/qq_31179577/article/details/76585854

PS:文章還在寫,目前都是一些概念性質的,想要做拓展的程式猿請過幾天再看,Drools會一致做完的~~~

1. 工欲善其事,必先利其器

Drools提供基於eclipse的IDE(這是可選的),但其核心只需要Java 1.5(Java SE)。

1.1 GEF安裝

Open the Help→Software updates…​→Available Software→Add Site…​ from the help menu. 
Location is:
http://download.eclipse.org/tools/gef/updates/releases/
PS:1.需要梯子
   2.官方文件給的截圖與目前版本差距太大,所以建議全安了吧。。。。
   3.挺慢的

2. 簡介

2.1 模組功能

Drools被分解成幾個模組,下面是組成JBoss Drools的重要庫的描述 :
jar 作用
knowledge-api.jar 這提供了介面和工廠。它還有助於清楚地顯示什麼是使用者API,什麼是引擎API
knowledge-internal-api.jar 這提供了內部介面和工廠
drools-core.jar 他是核心引擎,執行時元件。包含RETE引擎和LEAPS引擎。如果您正在預編譯規則(並通過包或RuleBase物件進行部署),這是惟一的執行時依賴性。
drools-compiler.jar 它包含編譯器/構建器元件,以獲取規則源,並構建可執行的規則庫。這通常是應用程式的執行時依賴性,但如果您預先編譯了規則,則不必如此。這取決於drools-core。
drools-jsr94.jar 這是jsr- 4相容實現,這實質上是drools編譯器元件的一個層。注意,由於jsr-94規範的性質,並不是所有特性都很容易通過這個介面公開。在某些情況下,直接使用Drools API比較容易,但是在某些環境中,jsr-94是強制執行的。
drools-decisiontables.jar 這是決策表的編譯器元件,它使用drools編譯器元件。這支援excel和CSV輸入格式。

2.2 依賴檔案

Maven pom.xml檔案
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-bom</artifactId>
        <type>pom</type>
        <version>...</version>
        <scope>import</scope>
      </dependency>
      ...
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.kie</groupId>
      <artifactId>kie-api</artifactId>
    </dependency>
    <dependency>
      <groupId>org.drools</groupId>
      <artifactId>drools-compiler</artifactId>
      <scope>runtime</scope>
    </dependency>
    ...
  <dependencies>

3.what?

3.1 規則執行生命週期

規則引擎優化了對條件的評估,並確保了我們可以以最快的方式確定命中的規則。然而, 除非我們指定,否則規則引擎不會在條件檢測時立即執行我們的業務規則 。當我們到達一個點時,我們發現一個規則對一組資料的評估是對的,那麼規則和觸發資料被新增到列表中。這是一個明確的規則生命週期的一部分,我們在規則評估與和規則執行之間有一個清晰的劃分。規則評估將會新增規則操作和觸發的資料到一個元件之中。我們稱這個元件是Agenda(議程)。規則執行是在命令中執行的。當我們通知規則引擎的時候,它應該會觸發我們議程上的所有的規則。

如前所述,我們不控制將要被命中的規則。而是根據我們建立的業務規則和我們為引擎提供的資料來確定這一點,但這其實是引擎的責任。但是,一旦引擎決定了應該命中的業務規則,我們可以控制它們執行命中的事件。這是通過對規則引擎的方法呼叫完成的。

一旦規則被觸發,每條規則將被執行。規則的執行可能會修改我們領域資料,然後這些更改如果引發了與新資料相匹配業務規則,新的規則將會被加入進Agenda(議程)中,或者如果這些修改導致匹配結果不再正確,那麼它將被取消。這個完整的迴圈將繼續下去,直到對於可用資料沒有更多的規則在議程中可用或規則引擎執行被迫停止。下面的圖 顯示該工作流是如何執行的:
這裡寫圖片描述

這個執行生命週期將繼續執行規則引擎根據決定根據規則的定義和域資料所決定的新增到議程中的所有的所有規則。有些規則可能不會觸發,有些規則可能會多次觸發。

3.2 why?

對於我們的業務規則的匹配而言。以傳統的命令式編碼(即我們java程式碼條件判斷等),如果業務規則發生了變化,我們需要重新從頭縷邏輯,然後在合適的地方新增/刪除、修改我們的條件判斷。這帶來就是業務邏輯的捆綁以及不可預知的Bug。而Drools提倡的是宣告式程式設計,我們只需要像做流水線一樣,單獨做自己的業務規則即可,每個業務規則均是獨立的,類似的,可以想象為if/else if/else變為了插拔式的多個if判斷。

3.3 how?

以領域驅動角度來談我們的領域層,其實最神祕的是業務邏輯與規則。比如移動的積分制度,保險的保金等等,他們都是與行業有關的知識。作為一個技術精湛的我們,也無法充分想到這些業務規則的邊邊角角。但是領域專家知道這些業務規則,可惜他們不會程式設計。

SO.BRMS(業務規則管理系統)就是以友好的方式,來使得領域專家書寫規則。

4. 規則引擎的演算法、

tell me:業務規則是如何執行的

規則引擎通過特定的演算法將我們定義的業務規則轉換為可執行決策樹。執行樹的效能 將取決於演算法生成的優化機制。是Drools6框架定義了自己的演算法,專注於更高的效能。這個演算法被稱為PHREAK,由Mark Proctor建立。它是基於一種叫做RETE的預先存在的演算法的一系列優化和重新設計的。作為開放原始碼實現,PHREAK是最高效、最有效的演算法之一。

在生成的執行樹中,規則中的每個條件都將被轉換為樹中的一個節點,以及再我們的規則中,不同的條件如何相互連線,將決定這些節點的連線方式。當我們在規則引擎中新增資料時 ,它將被批量評估,通過網路使用最優化的路徑。當資料到達代表了被觸發的規則的葉子時,執行樹完成。這些規則被新增到一個列表中,呼叫一個命令將會被要求觸發所有的規則或一組規則。

每次我們向規則引擎新增更多資料時,它都是通過執行樹的根元素引入的。執行樹的每一個優化都是根據 以下兩個主要焦點:
1.它會試圖將所有的條件分解為最小的單位 要儘可能多地重用執行樹
2.它將嘗試只進行一個操作,以達到下一個級別,直到它得到了一個false的條件評估或者執行到了頁節點上,在一個被標記為執行的規則上。

每一個數據都以可能的最有效的方式進行評估。這些評估的優化是規則引擎的主要關注點

4.1 什麼時候使用規則

什麼樣的專案適合將DFrools加入到技術站中??
1.他們定義了一個非常複雜的場景,甚至對於業務專家都很難完全定義
2.他們沒有已知的或定義明確的演算法解決方案
3.他們有揮發性的要求和需要經常更新
4.他們需要快速做出決定,通常是基於部分資料

其實你會發現,除了已經基本定型收工的專案,我們都可以將Drools加入技術棧

4.2 複雜的場景,簡單的規則

每隔一段那時間,我們會發現系統-或者是系統的一部分-元件間的小關係 開始變得越來越多,越來越重要。起先,他們可能看起來是無害的元件,只是依據兩三個資料來源來做一些業務判定。但是當以後我們再看它的時候,這些關係變得複雜了,而且也很重要。最終,我們可能 發現各部分之間的關係產生了更多的集體行為,甚至業務專家沒有意識到可能發生的事情;然而,這仍然是有意義的。這些系統被稱為複雜系統,它們是商業規則提供巨大幫助的地方之一。

複雜的場景通常由小的語句來定義。這個完整圖,涉及到完全定義場景所需的每一個組合、聚合或抽象,通常是超出我們最初的理解。因此,這類系統開始通過部分解釋來定義是很常見的。系統中的每個小關係都被定義為不同的 要求。當我們分析這些要求的每一個,把它們分開 在最基本的元素中,我們發現自己定義了業務規則

每個業務規則有助於定義複雜場景中的每一個小元件。隨著越來越多的規則被新增到系統中,越來越多的這些關係可以以一種簡單的方式來處理。沒一個規則都變為了系統在執行復雜場景時需要執行的每一個小的決策服務的自解釋的手冊。

複雜應用程式的例子可以非常多種多樣,如下所示:

Fraud detection systems欺詐檢測系統
為客戶定製零售優惠券:
信用評分的軟體

4.3 不斷變化的場景

參與做出一個特定的決定的要素往往會發生頻繁的變化,業務規則對於管理系統行為的這種波動,可以是一個很好的解決方案。

業務規則在規則引擎中表示為資料樹。同樣地,我們可以修改列表的元素,我們可以從業務規則裡移除或者新加業務規則。這可以實現不重啟專案和重新發布部署任何元件。Drools內部機制可以自動的更新規則。Drools 6提供的工具也準備為業務規則提供來自使用者友好編輯器的更新機制。Drools 6 API的完整架構是基於儘可能的自適應的。

如果我們發現一個系統,需求可能經常發生變化,即使是在每天或每小時的頻率,業務規則可能是最適合這些需求的,由於它的更新能力,而不管系統的複雜性。

4.4 網上商店example

首先,我們將定義我們的eShop系統的模型。該模型將包含所有與我們的申請有關的決策。 其中一些物件如下所示:

Domain 業務知識
Product 我們店裡想要出售不同種類的專案.每種型別都將由一個產品物件來表示,其中包含特定專案的詳細資訊。
Stock 這是我們儲存的每一種產品的數量。
Provider 我們的產品來自不同的供應商。每一個供應商以特定的交付能力,為eShop提供特定種類的產品。
Provider Request 當我們的運行了一段時間,某些商品已經過時或者昆村不夠時候,我們需要建立請求,來讓為提供商填充我們的庫存
Client 當客戶在我們的eShop中喜歡一個或多個產品時,他們可以訂購併支付他們。訂單有不同的狀態,取決於是否客戶成功地收到了它。他們也有關於具體產品它的資訊及其數量。
Discount 網店提供不同的折扣,取決於購買的型別
Sales channel 我們將模擬的eShop可以使用多個網站,每個站點都被視為不同的銷售渠道。每個銷售渠道將有它自己的特定目標受眾,這是由使用它的客戶決定的

在專案中,我們需要做到的是:
1.將產品與每個銷售渠道相關聯並將其與其他銷售渠道進行比較,來為特定種類的產品定義最佳銷售渠道。依託於這些資訊,我們可以為不同渠道的穿品建立自定義的折扣
2.定義特定產品的客戶機首選項。依託於這些資訊,我們可以為他們提供適合他們特定口味的折扣票。
3.確定特定產品的平均消費,並與我們的庫存進行比較。一旦需要的話,我們可以自動的觸發供應商請求來發貨。
4.根據我們對具體供應商的訂單數量,我們可以去爭取一個價格的折扣
5.我們可以分析客戶在eShops中所購買的不同商品。 如果,在某個時候,購買超出了我們認為的正常範圍,我們可以 採取一系列行動,從一個簡單的警告,到為特定的購買提供直接的人類支援。

4.5 if not

1.在這個專案中,很少有獨立的規則:如果在需求收集中確定的業務規則非常簡單,並且最多可以跨越一個或兩個物件,我們不需要一個規則引擎來執行他們
2.業務邏輯不會經常改變:
3.對於應用程式來說,非常嚴格的執行流控制至關重要:當我們執行業務規則的時候,沒有提供一個序列流控制。如果業務規則背後的業務邏輯有很大的依賴關係,對於業務規則需要按順序執行的嚴格步驟, 那麼規則引擎可能不合適。然而,如果它經常發生變化,可能是商業過程是值得考慮的。

下一節講書寫和執行規則

5. 環境配置

1.jdk 1.8
2.maven 3.1 above
3.Git 1.9 above

5.1 建立第一個Drools專案

1.地址:
git clone https://bitbucket.org/drools-6-developer-guide/drools6-dev-guide.git
2.Eclipse匯入專案二,不會的話,請關掉電腦,該幹嘛幹嘛去。。
3.首先看pom檔案,
    <dependencies>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <type>jar</type>
        </dependency>
        //上面的jar功能在第二節裡都講了~~
        //為了開始編寫我們自己域的規則,我們還需要新增一個依賴。下面這個依賴定義了由該域模型提供的域模型,這個在上一節我們也講過:
        <dependency>
            <groupId>org.drools.devguide</groupId>
            <artifactId>model</artifactId>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
 4./src/main/resources/META-INF/kmodule.xml
 這個檔案將被用於配置如何載入規則引擎中專案中定義的規則。就目前而言, kmodule.xml的內容將非常簡單,因為我們將使用所有的預設值配置。下節我們會講解如果配置這個檔案
5.專案的阻止結果已經確定了,我們現在來書寫和執行我們的第一個規則。
①在src/main/resources檔案下建立文字,這個檔案可不是text檔案,檔案拓展名以.drl標識。這樣它就可以被當作一個規則檔案
②檔案的擡頭與java檔案相似,分別是package和import
package myfirstproject.rules

import org.drools.devguide.eshop.model.Item;
import org.drools.devguide.eshop.model.Item.Category;
rule "Classify Item - Low Range"
    when
        $i: Item(cost < 200)
    then
        $i.setCategory(Category.LOW_RANGE);
end

這條規則檢查每一項成本低於200美元並自動以一個類別標記這個專案 。在這種情況下, 是將其標識以LOW_RANGE類別。對於我們的商店來說,區分不同種類的商品是有意義的,這樣我們就可以為他們應用不同的折扣和營銷策略。這個分類過程可以自動地使用規則來完成,這些規則集中了我們對LOW_RANGE、MID_RANGE或HIGH_RANGE專案的業務定義的點。

一般來說,這些檔案的結構如下:

package定義
import
(可選的)宣告的型別和事件
Rules: (1..N)/Queries (1..N)
③執行測試

    public static void main( String[] args )
    {
        System.out.println( "Bootstrapping the Rule Engine ..." );
        // Bootstrapping a Rule Engine Session
        KieServices ks = KieServices.Factory.get();
        KieContainer kContainer = ks.getKieClasspathContainer();
        KieSession kSession =  kContainer.newKieSession();

        Item item = new Item("A", 123.0,234.0);
        System.out.println( "Item Category: " + item.getCategory()); 
        kSession.insert(item);
        int fired = kSession.fireAllRules();
        System.out.println( "Number of Rules executed = " + fired );
        System.out.println( "Item Category: " + item.getCategory()); 

    }

正如您在前面的示例中看到的,有三個主要階段,如下所示以下幾點:

  1. 引導規則引擎會話: KieServices /KieContainer /KieSession的職責,我們會在下一節裡講。現在,我們只需要知道KieSession代表了一個具有指定配置和一系列規則的規則引擎的執行時例項。它掌握了與我們域物件的規則相匹配的評估演算法
  2. 讓規則引擎知道我們的資料:我們負責把所有的資訊都提供給引擎,這樣它就可以操作。為了做到這一點,我們在KieSession上使用了insert()方法。我們也可以使用delete()從規則引擎上下文刪除資訊方法或使用modify()方法更新資訊。
  3. 如果我們提供的資訊與一個或多個定義的規則相匹配,我們就就會得到匹配結果。呼叫fireAllRules()方法將執行這些匹配操作。在下一節,Drools執行時,我們將學習更多的匹配。

5.2 使用CDI引導規則引擎

Contexs and Dependency Injection (CDI) (網站 http://www.cdi-spec.org),是一組標準化的api,用於為我們的應用程式提供這些特性,因為它允許我們選擇我們所要的上下文和依賴注入容器。CDI現在已經成為Java SE規範的一部分,它的應用正在每年增長。由於這個原因,由於Drools專案增加了很多支援 CDI環境,本節簡要介紹如何簡化我們的Hello World 我們在上一節中所寫的例子。

為了在我們的專案中使用CDI,我們需要在我們的專案中新增一些依賴項 專案,如下:

    <dependencies>
        ....上述的jar,不列舉了.....
        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jboss.weld.se</groupId>
            <artifactId>weld-se-core</artifactId>
        </dependency>
    </dependencies>

javax.enterprise:cdi-api包含CDI規範中定義的所有介面

org.jboss.weld.se:weld-se-core包含我們將使用實現CDI介面的工具

通過添加了這兩個jar,我們可以在專案中使用@Inject註解,來注入KieSession,Weld容器將負責為我們提供規則引擎。

CDI的工作原理基於約定優於配置,它引入了新增新檔案的需要,檔案放在 src/main/resources/META-INF下的bean.xml檔案,用於配置容器如何在專案和其他配置中訪問我們的bean。請注意與我們之前介紹過的檔案kmodule. xml的相似性 。這是一個空的beans.xml的示例內容檔案,CDI容器使用該檔案瞭解需要解析的jar 將容器提供給@inject注入的bean,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

一旦我們有了容器的依賴項和beans.xml檔案,容器可以掃描類路徑(即我們的專案及其依賴項) 尋找要注入的bean,我們可以在應用程式中使用這些特性。

下面的類表示建立預設KieSession的同一個簡單示例,然後與它互動。

下面的程式碼片段通過CDI初始化KieSession並與它互動, 如下:

public class App 
{

    @Inject
    @KSession("") // This is not working track: https://issues.jboss.org/browse/DROOLS-755 
                   // it should be @KSession() for the default session
    KieSession kSession;

    public void go(PrintStream out){
        Item item = new Item("A", 123.0,234.0);
        out.println( "Item Category: " + item.getCategory()); 
        kSession.insert(item);
        int fired = kSession.fireAllRules();
        out.println( "Number of Rules executed = " + fired );
        out.println( "Item Category: " + item.getCategory()); 
    }

    public static void main( String[] args )
    {
        Weld w = new Weld();

        WeldContainer wc = w.initialize();
        App bean = wc.instance().select(App.class).get();
        bean.go(System.out);

        w.shutdown();             
    }
}

注意,main(…)方法現在正在引導Weld容器,由於這個原因,我們的bean(App)可以注入任何bean。在本例中,@ksession註釋負責引導引擎併為其建立一個新的例項 提供給我們使用。我們將看第3章中CDI擴充套件所提供的註解。

將此視為與Drools規則引擎互動的另一個非常有效的選項。 如果您正在使用一個Java EE容器,比如WildFly AS(http://www.wildfly.org/),它是建立在純粹基於CDI的核心之上的,那麼這種方式使可以使用的。

在本例中,我們使用WELD,他是CDI的參考實現。注意,您可以使用任何其他CDI實現,例如在 http://openwebbeans.apache.org中開啟Apache Open Web Beans。

現在,為了瞭解規則是如何被應用和執行的,我們應該清楚地理解我們用來寫規則的語言,我們稱呼它叫做Drools規則語言(DRL)。下面的部分將從不同角度更詳細的介紹語言。下一節將更詳細地介紹執行方面,當我們引導規則引擎會話時,以及如何為不同的目的配置它時,解釋了正在發生的事情。

5.3 規則語言

PS:參考專案為chapter-02-kjar
正如之前所屬的,規則是由條件和結果組成的
rule "name"
when 
    (Condition條件)--通常也叫做左手邊的規則(LHS)
then
    (Actions/Consequence)--通常也叫做右手邊的規則(RHS)
end

規則的LHS–條件,是根據DRL語言來編寫的(檔名都是.drl結尾。。。),為了簡單起見不會完全解釋。我們會在樣例裡看到最常用的一些DRL語言結構,我們會盡可能多地使用語言。你可以通過下面的地址來學習完整詳細的語言結構:

  https://docs.jboss.org/drools/release/7.1.0.Final/drools-docs/html_single/index.html#DroolsLanguageReferenceChapter
  • 1

規則的LHS由條件元素組成,它們充當過濾器,定義規則來評估真正的滿足的條件。這個條件元素過濾事實(即insert方法傳入的物件資料),在我們的例子中是我們在Java中工作的物件例項。

如果你看我們第一條規則的LHS,那個條件表示式非常簡單,如下:

rule "Classify Item - Low Range"
    when
        $i: Item(cost < 200)
    then
        $i.setCategory(Category.LOW_RANGE);
end

LHS可以劃分為三部分:

  • Item(…)是為Item型別的物件的過濾器。這個過濾器會拿起所有我們以insert方法進session中的item物件用於進行處理。
  • cost<200這個過濾器將會看一看每個item物件,確保cost屬性有一個值且小於200
  • i表示一個變數繫結,它用於以後引用匹配的物件。注意,我們使用
  • 符號來命名變數,這樣我們就能很容易地與物件欄位對比中識別出它們。這是好的做法。

綜上所述,我們基於物件及其屬性進行過濾。重要的是要理解,我們將過濾與這些條件匹配的物件例項。對於每一個對所有條件求真值的專案例項,規則引擎將建立一個匹配結果。

更復雜的規則可能是根據他們的訂單的大小來對我們的客戶進行分類。新規則和前一條規則有很大區別,現在的規則將需要評估訂單和客戶。看一下規則是:

rule "Classify Customer by order size"
    when
        $o: Order( orderLines.size >= 5, $customer: customer ) and
        $c: Customer(this == $customer, category == Customer.Category.NA)
    then
        modify($c){ 
            setCategory(Customer.Category.SILVER) 
        };
end 

在這個規則裡,我們正在對訂單數量超過5條的訂單進行評估,即表示五個不同的物品。然後,我們查詢與這個訂單管理關聯的消費者,並設定這個消費者的類別。消費者和訂單之間的關係是通過將訂單物件中的客戶引用繫結到一個叫做customer的變數,並通過下面的步驟來比較我們正在評估的客戶−>Customer(this==

customer…).條件元素的順序只由我們需要的繫結定義.在這種情況下,我們將Order.getCustomer()拿來去匹配消費者。但是,我們也可以用另一種方法來做,它會以同樣的方式工作,如下所示:

    $c:Customer(category == Customer.Category.NA)
    $o:Order(orderLines.size >= 5,customer == $c)

在這個端點上我們需要理解的是Customer(…條件)過濾器和Order(…條件)過濾器需要是事實,換句話說,他們需要去顯式的以insert方法a插入進KieSession中.儘管Order.getcustomer()不是事實,但是它是事實中的物件。

這個規則要想評估為true,我們需要一個Order和一個Customer物件來使得所有的條件為真。在Customer(…條件)過濾器和Order(…條件)之間,有一個隱式的AND,因此,這個LHS可以這樣解讀:這裡有一個超過20條記錄的訂單,AND,一個消費者與這個訂單相關聯。

那麼,現在我們再來看RHS,即modify(c)句子。modify()方法是由規則引擎提供的,來確保引擎知道了我們加入進規則引擎中的事實資料被修改了,並且這些修改需要通知其他的規則,因為這裡的改變可能會觸發其他的規則的匹配。既然這樣,我們正在讓引擎知道對消費者物件的類別的修改。如果您省略了modify(

c),則沒有其他規則知道該更改 類別,這意味著依賴於已分類條目的規則將不會被匹配的。由於這個原因,當在隸屬於classify-item-rules.drl檔案的規則中設定類別時,您會注意到我們也在更新專案事實。

現在我們的規則變得更加複雜了,我們在專案程式碼中清楚地區分業務定義,而注意這些個事實也很重要。我們正在提取如何將客戶分類為這些規則的定義,如果業務定義發生變化而不修改應用程式的其餘部分,我們將能夠更新這個定義。這是使用業務規則的核心概念之一。

現在,基於這種分類,我們可以為不同的客戶建立不同型別的優惠券,允許我們以不同的方式對待每一個客戶,依託於他們的忠誠值和以前的訂單:

rule "Create Coupons for Silver Customers"
    when
        $o: Order( $customer: customer )
        $c: Customer(this == $customer, category == Category.SILVER )
    then
        insert(new Coupon($c, $o, Coupon.CouponType.POINTS));        
end 

和前面的例子一樣,這裡的規則是由Order和Customer過濾; 然而,正如您在規則RHS中看到的,我們正在建立 Coupon優惠券的新物件並使用insert()方法將其插入進規則引擎讓其變為事實。這這意味著,一旦該規則被規則引擎執行,它就會觸發任何其他的期待著優惠券物件的規則。在這裡,事情變得更有趣了。我們瞭解了規則如何能夠生成新的資料並將不同的規則組合在一起當我們為規則引擎提供新資料時。

如果你覺得有必要做實驗,我們鼓勵你在 coupons-creation.drl 寫檔案裡一個規則去匹配建立的優惠券,看看會發生什麼

現在讓我們把事情變得更復雜一些,假設我們想要檢查一個包含兩個或多個專案的訂單,但是隻包含HIGH_RANGE專案,我們想要對這些具體的訂單申請一些折扣。

為了編寫一個檢查這種情況的規則,我們還需要對OrderLine物件(我們也需要新增這個匯入)進行評估 。這可以翻譯成在我們的規則中新增更多的過濾器。現在,我們需要對Order物件進行約束、OrderLines和相Item關聯。下面的UML圖顯示了 這些物件之間的關係:
這裡寫圖片描述

下面的規則檔案表達了先前引入的折扣規則:

rule "Silver Customers + High Range Order - 10% Discount"
    when

        $o: Order( $lines : orderLines.size >= 2, $customer: customer, discount == null )
        $c: Customer( category == Category.SILVER, this == $customer )
        forall( OrderLine( this memberOf $lines,  $item : item)
                Item(this == $item, category == Item.Category.HIGH_RANGE)
        )
    then
        $o.setDiscount(new Discount(10.0));
        update($o);
end

對於Order()、OrderLine()和Item(),我們有三種不同的物件篩選器。請注意 這條規則也取決於我們的物品分類;然而,這個規則和我們的第一個分類我們的物品的規則之間沒有明確的關係。 這個規則引入的一個新事物是條件元素forall,它確保所有的OrderLines和相關的訂單項都被分類為HIGH_RANGE物品。如果至少有一個專案與當前訂單有不同的類別集 ,此規則將不會被啟用和觸發。在和前面一樣,我們正在更新訂單,以便有另一條檢視折扣的規則被應用,資訊可以提供給引擎。
我們將分析這些專案結構和執行這些規則所提供的測試

5.4 組織我們的專案

我們的規則越複雜,對他們進行測試就越重要,並讓他們儘可能地組織起來。我們將討論如何組織我們的示例專案,以使我們的規則、它們的測試和相關的類保持在一個易於維護的結構中。

我們推薦這樣的方式來構建專案,這樣每個專案都有一個定義良好的範圍,依賴關係集,並且它們可以獨立測試.您應該保留所有的應用程式基礎設施程式碼(使用者介面,系統整合,服務,等)在單獨的Maven模組中,因此,基礎設施層可以在傾向於更頻繁地根據業務需要更新的業務知識的單獨迴圈中進行維護.

示例儲存庫包含一個高階的父專案,該專案由每個章節模組組成。每一章都包含以下三個模組.

-kjar:這將包含我們的業務資產,如規則、業務流程, 等等
-tests:我們將在這裡包括所有的測試
-extras (可選的):這個模組通常包含擴充套件的類 Drools和jBPM功能,比如規則的自定義評估器,業務流程的工作項處理程式,等等

-kjar和-tests模組被認為是知識專案,因為它們包含規則、定義和測試,以確保定義的知識行為正確。如下圖所示,*-test工程將會從你的應用中依賴領域模型工程。它也可能依賴於服務層來執行操作;然而,作為一種良好的實踐,這些服務可以被嘲笑。從專案的角度來看,最有可能的是,服務和使用者介面模組最終會依賴於知識相關的專案(就是-kjar,裡面是業務規則)。如果知識相關的專案只是定義了核心業務邏輯,您的應用程式裡的服務層將最終使用它們,以便在內部作出決策。從使用者介面的角度來看,我們還可以定義知識專案來幫助使用者在UI級別上,如下圖所示:
這裡寫圖片描述

在這一點上,不要擔心這個結構,它會變得更加清晰。現在,如果你看一下chapter-02-test/專案,您會發現我們需要定義一些額外的依賴項 我們將使用的測試框架如下:

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <scope>test</scope>
        </dependency>

在將這些依賴項新增到我們的專案之後,我們即可建立第一個Junit測試單元來使得而我們的規則可以按照預期的執行。注意,我們已經在這一章中定義了不同規則檔案中的幾個規則,現在,我們將把它們全部載入到相同的KieSession下。這意味著當我們你插入資訊進引擎中時,所有的規則都將會評估,並可能被觸發

我們要研究的第一個測試是 ClassifyItemsTest.java,位置在 src/tests/java/org/drools/devguide/chapter-02資料夾下的 chapter-02/chapter-02-test 專案裡

public class ClassifyItemsTest extends BaseTest {

    @Test
    public void lowRangeItemClassificationTest() {
        KieSession kSession = createDefaultSession();
        Item item = new Item("A", 123.0, 234.0);
        kSession.insert(item);
        int fired = kSession.fireAllRules();
        assertThat(1, is(fired));
        assertThat(Category.LOW_RANGE, is(item.getCategory()));
    }

//    @Test
//    public void midRangeItemClassificationTest() {
//    }

//    @Test
//    public void highRangeItemClassificationTest() {
//    }
}

這個測試展示瞭如何去確保我們的物品被分類正確。我們鼓勵去研究下classify-item-rules.drl檔案,檢視其它的規則檔案,並寫測試用例來確保其他類別也被正確處理。

如果我們的專案使用CI持續整合系統,例如jeekins,一旦規則改變,我們就會收到通知,其中一個測試會中斷。允許我們檢查測試是否需要更改,或者規則中引入的更改是否破壞了已經在系統中定義的其他一些策略。

另外的一個單元測試是OrderDiscountTest.java,在相同資料夾下。

public class OrderDiscountTest extends BaseTest {

    @Test
    public void highRangeOrderDiscountTest() {
        KieSession kSession = createDefaultSession();

        Order o = ModelFactory.getOrderWithFiveHighRangeItems();

        kSession.insert(o.getCustomer());
        kSession.insert(o.getOrderLines().get(0));
        kSession.insert(o.getOrderLines().get(1));
        kSession.insert(o.getOrderLines().get(2));
        kSession.insert(o.getOrderLines().get(3));
        kSession.insert(o.getOrderLines().get(4));
        kSession.insert(o.getOrderLines().get(0).getItem());
        kSession.insert(o.getOrderLines().get(1).getItem());
        kSession.insert(o.getOrderLines().get(2).getItem());
        kSession.insert(o.getOrderLines().get(3).getItem());
        kSession.insert(o.getOrderLines().get(4).getItem());
        kSession.insert(o);

        int fired = kSession.fireAllRules();

        // We have 5 Items that are categorized -> 5 rules were fired
        // We have 1 Customer that needs to be categorized -> 1 rule fired
        // We have just one order with all HIGH RAnge items -> 1 rule fired
        // One Coupon is created for the SILVER Customer -> 1 rule fired
        assertThat(8, is(fired));
        assertThat(o.getCustomer().getCategory(), is(Customer.Category.SILVER));
        assertThat(o.getDiscount(), not(nullValue()));
        assertThat(o.getDiscount().getPercentage(), is(10.0));
        assertThat(o.getOrderLines().get(0).getItem().getCategory(), is(Item.Category.HIGH_RANGE));
        assertThat(o.getOrderLines().get(1).getItem().getCategory(), is(Item.Category.HIGH_RANGE));
        assertThat(o.getOrderLines().get(2).getItem().getCategory(), is(Item.Category.HIGH_RANGE));
        assertThat(o.getOrderLines().get(3).getItem().getCategory(), is(Item.Category.HIGH_RANGE));
        assertThat(o.getOrderLines().get(4).getItem().getCategory(), is(Item.Category.HIGH_RANGE));
        // The Coupon Object was created by the Rule Engine so we need to get it from the KieSession
        Collection<Coupon> coupons = getFactsFromKieSession(kSession, Coupon.class);
        assertThat(1, is(coupons.size()));

    }
}

這些測試的一個常見需求是使用我們的域模型,這些領域物件可以包含複雜的結構和大量的資料。通常,我們最終會有一些助手從資料庫之類的資料儲存中檢索這些資訊,或者為了測試,我們可以建立本地例項。

對於這些測試,我們使用的是ModelFactory助手,它初始化了不同的訂單,客戶,以及我們的產品。

這個測試載入了我們迄今為止定義的所有規則(沒有特定的順序) DRL檔案:

classify-customer-rules.drl
classify-item-rules.drl
coupons-creation.drl
order-discount-rules.drl

KieSession是由助手方法createDefaultSession()建立的,ModelFactory為我們提供了一個訂單,讓我們可以訪問OrderLines,Customer 和 Items。為了使規則引擎能夠工作並將這些物件與之前定義的規則相匹配,我們需要使用insert()方法插入每個物件。你們可能已經注意到了,如果我們需要分別插入所有物件,那麼這很容易成為一個問題。 有幾個替代方案可以解決這個問題,我們將後面會講。我們需要知道我們是否有按型別過濾事實的規則。我們也需要將這些事實(就是資料)都加入進引擎中以供它使用。

現在,在將所有事實插入到KieSession之後,評評估由引擎完成,並建立匹配。重要的是要明白,並非所有的匹配都需要在fireAllRules()呼叫上執行,其中一些可能會被取消 ,或者還可以在執行過程中建立新的匹配。我們將在下一節詳細分析規則執行流程。現在,我們需要了解 為什麼我們要在測試中使用8條規則,為了做到這一點,我們需要做一些簡單的數學運算。

我們知道一個規則將被每一個需要分類的專案觸發。在這測試中,我們有5個物品條目物件Item,因此我們會有5條規則被觸發。我們不知道這些規則會被觸發的順序,然而,我們知道,他們將在一個依賴於這種分類的規則之前被觸發。同樣的情況也發生在顧客身上,因此,還有一條規則被觸發。同樣,我們不知道也不關心條目和客戶分類的順序。最後,為優惠券建立而觸發一個規則,再一個規則是向訂單應用折扣。正如我們之前描述的那樣,觸發命中的順並不重要,這個測試檢查是否物件會像預計那樣被改變。在測試結束後,我們無法檢查Coupon優惠券物件,因為我們沒有任何對於它的引用。這個Coupon優惠券物件被規則引擎建立,為了讓我們得到它,我們可以使用 getFactsFromSession ( kSession , Coupon.class ) 工具方法,這個方法可以獲取插入進KieSession中的Coupon物件事實。

如果你寫了更多關於優惠券、客戶、訂單等的規則,測試可能會失敗。如果你這樣做了,一定要更新測試,這樣他們就能通過。

**下一節,Drools執行時**

6.Drools Runtimes

6.1 理解Drools執行時例項

Drools允許我們以不用的方式建立規則引擎的例項,所以我們可以選擇最適合於我們正待解決的問題的方式。每一個規則引擎都是一個密封的上下文,我們定義的規則將根據我們提供給這個特定例項的資料進行評估。規則引擎被看作是在伺服器中執行它們的大而單一的程序,我們可以傳送資料給它處理。另一方面,Drools允許我們本地為我們的應用程式生成輕量級例項。通常有多個例項處理不同的規則和資料,而不僅僅是一個大例項。

為了生成規則引擎的新例項,我們需要了解以下概念:

KieService
KieContainer
KieModule
KieBase
KieSession

通過使用這五個概念,我們將能夠定義每個例項是如何配置的,以及每個例項對每個例項的可用性。在我們需要建立多個規則引擎的情況下,重要的是要了解究竟發生了什麼,以避免不必要的瓶頸和效能問題。重要的是要理解這五個概念是在Drools的前一個版本中提供的擴充套件版本。注意這裡的KIE字首,這表明,現在我們不僅在處理規則引擎例項,而且還提供了更多的定義和執行業務知識的方法。

首先,我們將從KieServices 類開始,通過提供一個服務登錄檔,我們可以找到不同目的的幫助方法,從而使我們能夠訪問所有其他的概念。在以後版本你的Drools,我們可以包含更多的服務來滿足不同的用例。而現在,我們需要知道如何獲取KieServices例項,我們通過使用下面的靜態kieservices.factory . get()方法來做到這一點:

    KiseServices ks = KieServices.Factory.get()

使用KieServices,我們可以訪問與規則引擎例項一起使用的許多工廠、服務和實用方法。我們將會使用KieService來建立KieContainer例項,它定義了用於建立規則引擎的新例項的規則的範圍。一個KieContainer可以承載一個KieModule和它的依賴,這意味著一個層次結構的KieModules可以被載入到KieContainer的一個例項中。

這些概念之間的關係如下圖所示:
這裡寫圖片描述

在Drools 6中,所有的東西都是圍繞著KieModules建立的。每個KieModule包含 在某一區域或某一領域相關的業務資產(業務規則、業務流程、決策表等)。這些KieModules可以包括其他的KieModules, 允許我們組成一個頂級的KieModule,包含來自不同領域的若干資產。

KieModule是一個包含規則的標準java-maven專案,業務流程和其他資源在其資源中。一個特殊的叫做kmodule.xml的檔案也必須包含在其中(存放在META-INF/dictory下),它定義了關於如何對這些特定資產進行分組和消費的內部配置。

正如我們在前面的圖中看到的,KieContainer將會允許我們去例項化一個KieModule,以便建立規則引擎的一個或多個例項。這些例項可以被配置為具有相同的規則或完全不同的設定。前面的圖還顯示瞭如何決定裝入哪個KieModule;例如,我們可以決定去載入KieModule A模組,因為我們要使用定義在其中的規則,或者我們可以直接載入其母類,即KieModule Parent,它依賴於KieMouduleA模組和KieModule B模組,因此這些模組中的每個配置都將被載入。

下面的部分將深入探討KieModule和KieContainer的細節。在開始的時候,這聽起來有點讓人困惑,因為有太多的選擇,這正是Drools為配置和例項化規則引擎所提供的靈活性的原因。

6.2 KieMoudule和KieContainer

一旦我們掌握了kieservice,我們就可以建立新的KieContainers。在內部, KieContainer對所有的業務資產(規則、流程、電子表格、PMML文件等)都有引用,當我們建立新的規則引擎例項時,這些資產將被載入。正如上一節所描述的,一個KieContainer根據我們的應用程式需要,可以生成具有不同配置的多個規則引擎例項,並承載不同的規則集.在Drools 6中,我們可以在兩個選項之間進行選擇,以定義將包含在KieContainer例項中的資源和配置的範圍,如下所示:

基於類路徑
使用Maven依賴解析技術(kie - ci)

第一個選項將檢視應用程式類路徑中的所有業務資產,並允許我們在規則引擎的不同例項中載入它們。第二個選項把預先定義的工件和它們的傳遞依賴項委託給Maven,以找出需要包含的所有資源。

讓我們詳細地看一下這兩個選項。首先,我們先來看看如何做到類路徑被掃描和規則被接收。瞭解該如何工作是為了瞭解本節所提供的專案。

本節提供了以下五個專案來演示我們的kiemodule的不同設定:

chapter-03-classpath-tests:這個專案提供了用於建立kiecontainer的類路徑解析策略的測試。
chapter-03-maven-tests:這個專案提供了測試,展示瞭如何利用Maven的力量來解決作為Maven構件的kiemodule
chapter-03-kjar-simple-discounts: 這是一個包含一套簡單折扣規則的kiemodule。
chapter-03-kjar-premium-discounts:這是另一個包含一套優惠折扣規則的kiemodule
chapter-03-kjar-parent:這是一個把持有對於上面的KieMoudule引用的模組。它不包含任何內部資產,但它提供了對簡單和高階折扣的參考,以便它們可以被一起引用。

接下來的兩部分將包括以下兩種選擇:

從classpth類路徑下載入規則
使用Maven構件載入規則(使用kie - ci)

6.2.1 從classpth類路徑下載入規則

參考 chapter-03-classpath-tests 專案

KieContainerClassPathTests類下的 loadingRulesFromClassPath()演示瞭如何可以通過掃描當前應用程式類路徑來建立新的KieContainer。在這個例子中,我們在JUnit測試中引導Drools,而Maven則負責根據pom.xml中的定義為我們設定類路徑。如果我們不適用maven來啟動這個專案(或者這個測試類),那麼Drools將會掃描專案的classpath類路徑,不管它是如何定義的。在這個例子中,就像我們在Maven專案中,為專案定義的所有依賴項和測試範圍定義的依賴項將在執行測試之前被新增到類路徑中。

讓我們來看已開pom.xml檔案,我們將注意到,三個包含kmodulexml檔案的依賴專案。,如下:

<dependency>
      <groupId>org.drools.devguide</groupId>
      <artifactId>chapter-03-kjar-simple-discounts</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.drools.devguide</groupId>
      <artifactId>chapter-03-kjar-premium-discounts</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.drools.devguide</groupId>
      <artifactId>chapter-03-kjar-parent</artifactId>
      <version>1.0.0</version>
      <scope>test</scope>
    </dependency>

當我們基於classpath類路徑建立一個KieContainer時候,所有的可用的jar將會被掃描。要建立一個新的KieContainer,我們使用KieSevice(ks)來為我們提供一份KieContainer例項,如下:

KieContainer kContainer = ks.newKieClasspathContainer();

Drools將會掃描所有類路徑下的jar,來在META-INF資料夾下尋找kmoufle.xml檔案。當找到時,這個檔案將載入所提供的配置,使它們可以在我們的應用程式中使用。對於這個特定的例子,會有4個kmoudle.xml檔案被發現,其中3個是在pom檔案中以依賴項定義的,另一個就是本專案自身的kmoudle.xml檔案。每一個kmoudle.xml檔案中的配置都會載入並提供給KieContainer,等待被使用。當然,注意在有一個叫classpath-discount-rules.drl的DRL檔案,在chapter-03-kjar-simple-discounts專案下的\src\main\resources\rules\simple\裡。

現在,我們的KieContainer已經載入了所有這些配置,我們可以開始馬上使用。回到我們的loadingRulesFromClassPath()測試,該測試顯示規則如何從類路徑載入,如下:

 KieServices ks = KieServices.Factory.get();
        KieContainer kContainer = ks.newKieClasspathContainer();

        Results results = kContainer.verify();
        results.getMessages().stream().forEach((message) -> {
            System.out.println(">> Message ( "+message.getLevel()+" ): "+message.getText());
        });
        assertThat(false, is(results.hasMessages(Message.Level.ERROR)));
        kContainer.getKieBaseNames().stream().map((kieBase) -> {
            System.out.println(">> Loading KieBase: "+ kieBase );
            return kieBase;
        }).forEach((kieBase) -> {
            kContainer.getKieSessionNamesInKieBase(kieBase).stream().forEach((kieSession) -> {
                System.out.println("\t >> Containing KieSession: "+ kieSession );
            });
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

如我們所見,verify()方法將允許我們確保我們的業務資產是正確的,並在KieContainer例項中正確載入。如果 verify()方法返回包含錯誤的結果,我們應該在這些錯誤再向前移動之前停止和糾正。注意,results.getmessages()方法返回的Message物件還包含問題所在的檔案中的行和列。如果一切正常,我們可以繼續檢查是否所有我們期望使用的KieBases和kiesUNK是被載入的。對於這個測試,我們只是列印了一下,然而,這裡建議使用斷言。

如前所述,kmodule.xml檔案包含了將提供給該KieContainer的內容,因此,讓我們來看看kmodule.xml檔案內容,來更深的理解在建立一個KieContainer類路徑時候都載入了什麼。

這個簡單的測試演示瞭如何基於類路徑掃描將所有的kiemodule載入到容器中 .確保您執行這個測試來驗證結果,並理解為什麼要載入不同的配置。

6.2.2 使用MAVEN載入規則(Kie-CI)

上面的小節裡講了,在類路徑中找到的所有KieModules,應用程式會將其載入到KieContainer中。有些情況下我們不希望這種情況發生。可能是因為我們在本地沒有專案的這些jars,或者我們想要從業務規則的依賴上分離我們的應用程式依賴關係。

如果我們開啟 chapter-03-maven-tests專案,檢視他的pom檔案,我們會發現有巨大的不同。這個檔案裡沒有一個KieMoudle依賴。然而,一個新的依賴被加入到了kie-ci 模組內。如下:

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-ci</artifactId>
            <type>jar</type>
            <scope>test</scope>
        </dependency>

這個新加入的依賴項,可以允許Drools使用Maven的機制去解決在應用程式類路徑之外的artifacts。讓我們看一看 KieContainerMavenTests測試類。我們看到我們可以使用KieService以一種不同的方式來建立一個新的容器,就像下面的這樣:

        KieServices ks = KieServices.Factory.get();
        KieContainer kContainer = ks.newKieContainer(ks.newReleaseId("org.drools.devguide", 
                                                                    "chapter-03-kjar-simple-discounts", 
                                                                    "1.0.0"));

現在請注意,我們沒有使用maven的poms檔案來依賴於其他的專案或者應用,而是代替以KieContainer根據我們所提供的GroupId, ArtifactId,和Version (也叫做GAV)來處理一個artifact。正如你看到的,newKieContainer()方法期待一個ReleaseId物件作為引數,這個物件是用KieServices的助手方法來完成建立的。

Drools的API不僅允許我們使用ReleaseId來指定KieMoudle的版本,而且如果需要的話,還可以升級到新版本,即通過updateToVersion()方法。這個方法會重新建立KieContainer,來使得它變為一個接入點,將KieBase和新版本的KieModule的KieSession連線起來的。

使用這種機制,容器將會從這個artifact以及這個artifact的依賴的配置項。如果你執行這個測試,你將會看到每一個單獨的測試只是載入在KieContainer建立時請求的那個artifact裡的kmoudle.xml檔案。

現在是時間來看一下kmoufle.xml檔案了,它允許我們配置我們的KieBase和KieSession,來準確定義瞭如何建立規則引擎例項以供我們在應用程式中使用

6.3 KieMoudle配置(KieBases,KieSession&StatelessSession)

kmoubdle.xml被用於自定義KieMoudle配置。在這個檔案裡,我們可以定義規則如何在不同的KieBase裡組織在一起,KieBase可以以不同的目的被載入。它還允許我們去為將會建立的規則引擎例項定義更多的細粒度的配置。

在這一以小節裡,我們將會覆蓋KieBase,KieSeession和StateKieSeeion的基礎配置。在最後,我們也會回顧一種機制,我們可以使用這種機制,來讓其他Kiemoudle裡的KieBase加入進我們的KieMoudle中。

首先看chapter-03-classpath-tests/src/test/resources/META-INF/下的kmoudle.xml檔案

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <kbase name="rules.cp.discount">
        <ksession name="rules.cp.discount.session" type="stateful"/>

    </kbase>
</kmodule>

使用這個KieBase和KieSession的概念,我們可以定義如何載入規則的粒度。KieBase表示一組資產(即規則等)的編譯版本。而一個KieSession則是一個包含KieBase裡的規則的規則引擎的例項。為此,對於一個相同的KieBase,我們可以有多個不同會話例項,而且這是有意義的。我們可以使用相同的規則,但是有一個以不同的方式為不同的需求配置的會話。

在本例子中,我們定義了KieBase叫做rules.cp.discount.注意,KieBase的name屬性與我們在src/test/resources/下所使用的檔案結構相同,其實也是我們規則的所儲存的地方。 在KieBase中,我們定義了一個叫做rules.cp.discount.session 的KieSession。這個KieSession代表了包含所有在KieBase中定義的規則的規則引擎例項。在Drools6中,類似於以前的Drools版本,KieSession有兩種狀態型別(以前叫做KnowledgeSession):Stateful和Stateless

stateful狀態的KieSession允許我們在與規則引擎的幾次互動時保持這種狀態。在Drools6, Stateful Knowledge Sessions改名為KieSession,因為有最常見的會話型別,名字就保持的較短一點。相比之下,StatelessKieSession只允許我們互動一次,取得結果,沒有為下一個互動儲存狀態。我們將會在之後章節將KieSession。

例如,如果你看一下測試,KieContainerClasspathTests類下的loadingRulesFromDependencyParentKieModule()方法,你可能注意到我們使用一個(StateFul)的KieSession插入了一些列的事實資料,然後我們呼叫fireAllRules()方法來,然後我們不斷插入更多的事實資料,並再次呼叫fireAllRules()。在兩次呼叫 fireAllRules()之間,狀態(即事實資料和規則的評估)被保持了下來。在某些情況下,第二組事實與第一組事實一起觸發新的規則,如下:

        KieSession kieSession = kContainer.newKieSession("rules.discount.all");

        Customer customer = new Customer();
        customer.setCustomerId(1L);
        customer.setCategory(Customer.Category.SILVER);

        Order order = new Order();
        order.setCustomer(customer);

        kieSession.insert(customer);
        kieSession.insert(order);

        int fired = kieSession.fireAllRules();

        assertThat(1, is(fired));
        assertThat(10.0, is(order.getDiscount().getPercentage()));


        Customer customerGold = new Customer();
        customerGold.setCustomerId(2L);
        customerGold.setCategory(Customer.Category.GOLD);

        Order orderGold = new Order();
        orderGold.setCustomer(customerGold);

        kieSession.insert(customerGold);
        kieSession.insert(orderGold);

        fired = kieSession.fireAllRules();

        assertThat(1, is(fired));
        assertThat(20.0, is(orderGold.getDiscount().getPercentage()));

正如你看到的,分類為SILVER的訂單和消費者customer,在我們插入了分類GOLD的消費者和訂單後,仍然在KieSession中,即說明狀態被保持了下來。下一節我們會學如何小更復雜的規則,例如,評估兩個訂單及其關係。在這樣的情況下,不止一條規則可以被觸發。

現在我們來看看 statelessSessionTest()這個測試,這是一個StateLess的KieSeesion的例子,我們可以與之前的Stateful的KieSesion的進行對比。當然首先我們需要在kmoudle.xml檔案裡定義stateless的KieSession。

<ksession name="rules.cp.discount.session" type="stateful"/>

 

現在我們通過我們的KieContainer;來獲取一個statelessSession例項。如下:

        StatelessKieSession statelessKieSession = kContainer.newStatelessKieSession("rules.simple.sl.discount");

        Customer customer = new Customer();
        customer.setCategory(Customer.Category.SILVER);

        Order order = new Order();
        order.setCustomer(customer);

        Command newInsertOrder = ks.getCommands().newInsert(order, "orderOut");
        Command newInsertCustomer = ks.getCommands().newInsert(customer);
        Command newFireAllRules = ks.getCommands().newFireAllRules("outFired");
        List<Command> cmds = new ArrayList<Command>();
        cmds.add(newInsertOrder);
        cmds.add(newInsertCustomer);
        cmds.add(newFireAllRules);
        ExecutionResults execResults = statelessKieSession.execute(ks.getCommands().newBatchExecution(cmds));

        order = (Order)execResults.getValue("orderOut");
        int fired = (Integer)execResults.getValue("outFired");

        assertThat(1, is(fired));
        assertThat(10.0, is(order.getDiscount().getPercentage()));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

注意現在我們需要使用newStatelessKieSession()方法來建立新的StateLessKieSession.這個型別的會話,我們不需要有insert方法和fireAllRules()方法,代替的是使用execute()方法,來讓我們傳送一個命令或者一串命令去執行。從這個執行,你可以獲取到一個 ExecutionResults物件,它會包含所有的我們想要在規則執行後收集的結果集。然而在互動之後(執行並取得了執行結果),statelessKieSession就不會儲存任何狀態,如果你又執行execut(),那麼所有的規則只會根據我們傳送給這個互動的事實資料來重新評估。

最終,你如果想聚合KieMoudle,我們可以使用