用戶手冊
基礎
無狀態的知識Session
Drools規則引擎擁有大量的用例和功能,我們要如何開始?你無須擔心,這些復雜性是分層的,你可以用簡單的用例來逐步入門。
無狀態Session,無須使用推理,就形成了最簡單的用例。一個無狀態session可以經過一個函數,然后返回一些結果。無狀態session的用例都具有但不限于如下功能:
-
校驗
-
此人是否有資格申請抵押貸款
-
-
計算
-
計算抵押溢價
-
-
路由和過濾
-
將傳入的郵件(如電子郵件)過濾到文件夾中
-
將傳入的消息發送到目的地
-
讓我們從一個簡單的例子開始,使用一個駕駛執照申請。
public class Applicant {private String name; private int age; private boolean valid; // getter and setter methods here }
根據上面的數據模型,我們為它添加一個規則:18歲以下的申請人會被拒絕。
package com.company.licenserule "Is of valid age"
when$a : Applicant( age < 18 )
then$a.setValid( false );
end
為了使規則引擎能夠識別數據,從而能夠針對一些規則進行處理,我們需要?插入?數據,就像一個數據庫一樣。當Applicant實例被插入到引擎時,首先會校驗約束條件,在這個用例中只有兩個約束條件。之所以說是兩個規則,其一是Applicant的類型校驗,其二是age < 18
的規則校驗。一個對象類型加上它的另個或多個字段約束稱為模式。當插入的實例同時滿足這兩個約束時,那么認為實例能夠匹配這一規則。
$a
是一個綁定變量,它允許我們引用匹配約束的對象實例。這一實例的屬性在then后面會被更新,('$')符號是可選的,但它能夠幫助用來從不同的字段名中區分不同的變量名。僅僅處理被插入的數據中能夠匹配模式的數據的這種做法,我們稱為模式匹配。
在Drools文件中推入規則是十分必要的,它只是一個普通的文本文件,只是后綴名是 .drl,它是 "Drools Rule Language" 的簡稱。那么讓我們來調用一下文件 licenseApplication.drl ,然后在Kie Project中保存。 Kie Project具有一個正常的Maven項目結構,還一個附加的定義了KieBase
s 和?KieSession
?的 kmodule.xml文件。這個文件必須放在Maven項目的resources / META-INF文件夾中,而包含前一個規則的所有其他Drools構件(如licenseApplication.drl)必須存儲在資源文件夾或其下的任何其他子文件夾中。
由于所有配置方面都提供了有意義的默認值,所以最簡單的kmodule.xml文件只需包含一個空的kmodule標簽,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule"/>
在這一點上,可以創建一個KieContainer
來從類路徑中讀取要生成的文件。
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
上面的代碼片段編譯了在classpath中找到的所有DRL文件,并把這個編譯的結果,一個KieModule
,放到KieContainer
中。 如果沒有錯誤,我們將準備從“KieContainer”創建會話并針對一些數據執行:
StatelessKieSession kSession = kContainer.newStatelessKieSession();
Applicant applicant = new Applicant( "Mr John Smith", 16 );
assertTrue( applicant.isValid() );
ksession.execute( applicant );
assertFalse( applicant.isValid() );
上面的代碼根據規則執行數據。 由于申請人年齡在18歲以下,申請被標記為無效。
到目前為止,我們只使用了一個實例,但如果我們想要使用多個實例呢? 我們可以針對任何實現Iterable的對象執行,比如集合。 讓我們添加另一個名為“Application”的類,它具有應用程序的日期,我們也將布爾有效字段移動到“Application”類。
public class Applicant {private String name; private int age; // getter and setter methods here } public class Application { private Date dateApplied; private boolean valid; // getter and setter methods here }
我們還會添加另一條規則來驗證申請是在一段時間內完成的。
package com.company.licenserule "Is of valid age"
whenApplicant( age < 18 ) $a : Application() then $a.setValid( false ); end rule "Application was made this year" when $a : Application( dateApplied > "01-jan-2009" ) then $a.setValid( false ); end
不幸的是,Java數組并沒有實現?Iterable
?接口,所以我們必須使用JDK轉換器方法?Arrays.asList(…?)
。 下面顯示的代碼針對可迭代的列表執行,在列表中插入所有集合元素,然后再觸發任何匹配的規則。
StatelessKieSession kSession = kContainer.newStatelessKieSession();
Applicant applicant = new Applicant( "Mr John Smith", 16 );
Application application = new Application();
assertTrue( application.isValid() );
ksession.execute( Arrays.asList( new Object[] { application, applicant } ) ); assertFalse( application.isValid() );
execute(Object object)
?和?execute(Iterable objects )
?是接口BatchExecutor方法?execute(Command command)
?的兩個實現方法。
像KIE API的所有其他工廠一樣,可以從?KieServices
?獲得的?KieCommands
?命令工廠用于創建命令,以下相當于?execute(Iterable it)
:
``ksession.execute( kieServices.getCommands().newInsertElements( Arrays.asList( new Object[] { application, applicant } ) );``
批處理執行程序和命令工廠在使用多個命令和輸出標識符來獲取結果時特別有用。
KieCommands kieCommands = kieServices.getCommands();
List<Command> cmds = new ArrayList<Command>();
cmds.add( kieCommands.newInsert( new Person( "Mr John Smith" ), "mrSmith", true, null ) ); cmds.add( kieCommands.newInsert( new Person( "Mr John Doe" ), "mrDoe", true, null ) ); BatchExecutionResults results = ksession.execute( kieCommands.newBatchExecution( cmds ) ); assertEquals( new Person( "Mr John Smith" ), results.getValue( "mrSmith" ) );
CommandFactory
?支持許多其他可以在?StartProcess
?,?Query
?和?SetGlobal
?的?BatchExecutor
?中使用的命令。
有狀態的知識Session
有狀態會話很長時間,并允許隨著時間的推移進行迭代更改。有狀態會話的一些常見用例包括但不限于:
-
監測
-
半自動買入的股市監測和分析
-
-
診斷
-
故障查找,醫療診斷
-
-
物流
-
包裹跟蹤和交付供應
-
-
合規性
-
驗證市場交易的合法性
-
與無狀態會話相比,之后必須調用dispose()方法,以確保沒有內存泄漏,因為KieBase在創建時包含對有狀態知識會話的引用。由于有狀態知識會話是最常用的會話類型,因此它在KIE API中被命名為KieSession
。KieSession
還支持BatchExecutor
接口,就像StatelessKieSession
一樣,唯一的區別是FireAllRules
命令不會在有狀態會話結束時自動調用。
我們以舉一個火警的例子來說明監控用例。我們只用四個類,代表一間房子里的房間,每間房子里有一個灑水器。如果一個房間發生火災,我們用一個“Fire”實例來表示。
public class Room {private String name // getter and setter methods here } public class Sprinkler { private Room room; private boolean on; // getter and setter methods here } public class Fire { private Room room; // getter and setter methods here } public class Alarm { }
在前面關于無狀態會話的章節中,介紹了插入和匹配數據的概念。 這個例子假設每個對象類型只有一個實例被插入,因此只能使用字面約束。 然而,房子有很多房間,所以規則必須表達物體之間的關系,比如噴灑器在某個房間里。 這最好通過使用綁定變量作為模式中的約束來完成。 這個“加入”過程產生了所謂的交叉產品,這在下一節將會介紹。
發生火災時,會為該房間創建Fire
類的實例,并將其插入會話中。 該規則在Fire
對象的room
字段上使用綁定來約束與當前關閉的房間的噴灑器的匹配。 當這個規則觸發并且結果被執行時,噴淋器被打開。
rule "When there is a fire turn on the sprinkler"
whenFire($room : room)$sprinkler : Sprinkler( room == $room, on == false ) then modify( $sprinkler ) { setOn( true ) }; System.out.println( "Turn on the sprinkler for room " + $room.getName() ); end
而無狀態會話使用標準的Java語法來修改一個字段,而在上面的規則中,我們使用modify
語句,它作為一種“with”語句。它可能包含一系列逗號分隔的Java表達式,即對由modify
語句的控制表達式選擇的對象的設置者的調用。這會修改數據,并使引擎知道這些更改,以便再次推理它們。這個過程被稱為推理,對有狀態會話的工作是必不可少的。無狀態會話通常不使用推理,因此引擎不需要知道數據的更改。也可以通過使用sequential mode顯式關閉推理。
到目前為止,我們有規則告訴我們什么時候匹配數據存在,但是什么時候存在not呢? 我們如何確定火已被撲滅,即不存在一個Fire
對象了嗎? 以前,約束條件是根據命題邏輯的句子,其中引擎限制個別情況。 Drools的也有一階邏輯的支持,可以讓你看的數據集。 當不存在某個關鍵字時,關鍵字“not”下的模式匹配。 一旦房間里的火已經消失,下面給出的規則就會使噴灑器關閉。
rule "When the fire is gone turn off the sprinkler"
when$room : Room( )$sprinkler : Sprinkler( room == $room, on == true )not Fire( room == $room ) then modify( $sprinkler ) { setOn( false ) }; System.out.println( "Turn off the sprinkler for room " + $room.getName() ); end
每個房間有一個灑水噴頭,這個建筑物只有一個警報。 發生火災時會產生一個“Alarm”對象,但無論發生多少火災,整個建筑物只需要一個“Alarm”。 以前沒有引入“not”來匹配事實; 現在我們使用它的補充“exists”來匹配某個類別的一個或多個實例。
rule "Raise the alarm when we have one or more fires"
whenexists Fire()
theninsert( new Alarm() ); System.out.println( "Raise the alarm" ); end
同樣,當沒有火災時,我們要刪除警報,所以not
關鍵字可以再次使用。
rule "Cancel the alarm when all the fires have gone"
whennot Fire()$alarm : Alarm() then delete( $alarm ); System.out.println( "Cancel the alarm" ); end
最后,當應用程序首次啟動時,以及在警報被移除并且所有灑水噴頭已關閉之后,都會打印一般健康狀態消息。
rule "Status output when things are ok"
whennot Alarm()not Sprinkler( on == true ) then System.out.println( "Everything is ok" ); end
正如我們在無狀態會話中所做的那樣,上面的規則應該放在一個DRL文件中,并保存到Maven項目或其任何子文件夾的資源文件夾中。 和以前一樣,我們可以從KieContainer
中獲得一個KieSession
。 唯一不同的是,這一次我們創建了一個有狀態會話,而在此之前,我們創建的是一個無狀態會話。
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
KieSession ksession = kContainer.newKieSession();
隨著會話創建,現在可以反復使用它隨著時間的推移。 創建并插入四個Room
對象,以及每個房間的一個sprinkler
對象。 在這一點上,引擎已經完成了所有的匹配,但是還沒有任何規則被解雇。 調用ksession.fireAllRules()
允許匹配的規則觸發,但沒有火災,只會產生健康信息。
String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"}; Map<String,Room> name2room = new HashMap<String,Room>(); for( String name: names ){ Room room = new Room( name ); name2room.put( name, room ); ksession.insert( room ); Sprinkler sprinkler = new Sprinkler( room ); ksession.insert( sprinkler ); } ksession.fireAllRules();
``> Everything is ok``
我們現在創建兩個大火并插入它們; 這次為返回的FactHandle
保留一個引用。 事實句柄是插入實例的內部引擎引用,允許實例在稍后的時間點收回或修改。 現在發動機發生火災,一旦調用了fireAllRules(),報警就會被觸發,相應的噴淋頭就會打開。
Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
Fire officeFire = new Fire( name2room.get( "office" ) );FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
FactHandle officeFireHandle = ksession.insert( officeFire );ksession.fireAllRules();
> Raise the alarm
> Turn on the sprinkler for room kitchen
> Turn on the sprinkler for room office
過了一段時間后,火勢將會消失,Fire
實例將被收回。 這導致灑水器關閉,警報被取消,最終再次打印健康信息。
ksession.delete( kitchenFireHandle );
ksession.delete( officeFireHandle );ksession.fireAllRules();
> Cancel the alarm
> Turn off the sprinkler for room office
> Turn off the sprinkler for room kitchen
> Everything is ok
這并不難,我希望你能開始看到聲明式規則系統的價值和威力。
方法與規則
人們經常混淆方法和規則,而新規則用戶經常會問:“我怎樣稱呼規則?” 在上一節之后,你現在的感覺就像一個規則專家,答案是顯而易見的,但我們仍然總結的差異。
public void helloWorld(Person person) { if ( person.getName().equals( "Chuck" ) ) { System.out.println( "Hello Chuck" ); } }
-
方法直接調用。
-
特定的實例通過。
-
一次調用會導致一次執行。
rule "Hello World" whenPerson( name == "Chuck" ) then System.out.println( "Hello Chuck" ); end
-
規則通過匹配任何數據執行,只要它插入引擎。
-
規則永遠不能直接調用。
-
特定的實例不能傳遞給規則。
-
根據比賽情況,規則可能會觸發一次或幾次,或根本不觸發。
交叉產品
早些時候提到了“交叉產品”這個詞,這是加入的結果。 想象一下,來自火災報警示例的數據與沒有字段限制的下列規則結合使用:
rule "Show Sprinklers" when$room : Room()$sprinkler : Sprinkler()
thenSystem.out.println( "room:" + $room.getName() +" sprinkler:" + $sprinkler.getRoom().getName() );
end
用SQL語言來說,就像在Room
,?Sprinkler
中執行select *
一樣,Room表中的每一行都將與Sprinkler表中的每一行連接在一起,產生以下輸出:
room:office sprinkler:office
room:office sprinkler:kitchen
room:office sprinkler:livingroom
room:office sprinkler:bedroom
room:kitchen sprinkler:office
room:kitchen sprinkler:kitchen
room:kitchen sprinkler:livingroom
room:kitchen sprinkler:bedroom
room:livingroom sprinkler:office
room:livingroom sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:livingroom sprinkler:bedroom
room:bedroom sprinkler:office
room:bedroom sprinkler:kitchen
room:bedroom sprinkler:livingroom
room:bedroom sprinkler:bedroom
這些交叉產品顯然可能變得龐大,并且可能包含虛假數據。 交叉產品的大小通常是新規則制定者性能問題的來源。 由此可以看出,限制交叉產品總是可取的,這是通過可變約束來完成的。
rule
when$room : Room()$sprinkler : Sprinkler( room == $room )
thenSystem.out.println( "room:" + $room.getName() +" sprinkler:" + $sprinkler.getRoom().getName() );
end
這使得只有四行數據,每個房間都有正確的噴淋頭。 在SQL(實際上是HQL)中,相應的查詢將是select * from Room, Sprinkler where Room == Sprinkler.room
。
room:office sprinkler:office
room:kitchen sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:bedroom sprinkler:bedroom
執行控制
議程
議程是一個Rete功能。 它維護一組能夠執行的規則,其工作是按照確定的順序安排執行。
在RuleRuntime
操作期間,規則可能完全匹配并有資格執行; 單個規則運行時操作可能會導致多個符合條件的規則。 當規則完全匹配時,創建規則匹配,引用規則和匹配的事實,并放置到議程中。 議程使用沖突解決策略控制這些匹配的執行順序。
引擎反復循環兩個階段:
1.規則運行時操作。 這是大部分工作發生的地方,無論是在結果(RHS本身)還是主要的Java應用程序過程中。 一旦結果完成或主Java應用程序進程調用fireAllRules()
,引擎就切換到議程評估階段。
2.議程評估。 這試圖選擇一個規則來觸發。 如果沒有找到規則,則退出,否則會觸發找到的規則,將階段切換回規則運行時操作。

這個過程重復,直到議程清晰,在這種情況下控制返回到調用應用程序。 當規則運行時操作正在發生時,沒有規則被觸發。
規則匹配和沖突集
現金流量示例
到目前為止,數據和匹配過程是簡單而小巧的。 為了將事情混合起來,我們將探索一個新的例子來處理日期期間的現金流量計算。 發動機的狀態將在關鍵階段示例性地示出,以幫助更好地理解發動機罩下的實際情況。 將使用三個類,如下所示。 這將有助于我們增加對模式匹配的理解并進一步加入。 然后我們將用這個來說明執行控制的不同技術。
public class CashFlow {private Date date; private double amount; private int type; long accountNo; // getter and setter methods here } public class Account { private long accountNo; private double balance; // getter and setter methods here } public AccountPeriod { private Date start; private Date end; // getter and setter methods here }
現在,您已經知道如何創建KieBases以及如何實例化事實來填充KieSession,所以將使用表來顯示插入的數據的狀態,因為它使事情變得更加清楚。 下表顯示為“Account”插入單個事實。 還插入了一系列借方和貸方作為該賬戶的“CashFlow”對象,延伸了兩個季度。

兩個規則可以用來確定該季度的借方和貸方,并更新賬戶余額。 以下兩條規則限制了給定時間段內某個賬戶的現金流量。 請注意使用簡短語法的“&&”,以避免兩次重復字段名稱。
select * from Account acc, Cashflow cf, AccountPeriod ap where acc.accountNo == cf.accountNo and cf.type == CREDIT and cf.date >= ap.start and cf.date <= ap.end
trigger : acc.balance += cf.amount
select * from Account acc, Cashflow cf, AccountPeriod ap where acc.accountNo == cf.accountNo and cf.type == DEBIT and cf.date >= ap.start and cf.date <= ap.end
trigger : acc.balance -= cf.amount
如果AccountPeriod設置為第一季度,我們將限制增加貸方余額
的規則在兩行數據上觸發,減少借方余額
以對一行數據進行操作。

上面的兩個現金流量表代表兩個規則的匹配數據。 數據在插入階段被匹配,正如你在上一章中發現的那樣,它不會直接觸發,而只是在fireAllRules()
被調用之后。 同時,規則及其匹配的數據被放置在議程上,并被稱為“行為匹配”或“規則實例”。 議程是一個規則匹配表,只要fireAllRules()被調用,它就能夠觸發并執行其后果。 議程上的規則匹配被稱為沖突集,其執行是由沖突解決策略決定的。 請注意,到目前為止的執行順序被認為是任意的。

所有上述激活的規則被執行后,賬戶的余額為-25。

如果賬戶期限更新到第二季度,我們只有一行匹配的數據,因此在議程上只有一個規則匹配。
規則執行的結果導致了25的余額。


解決沖突
如果你不希望規則執行的順序是任意的呢? 如果議程上有一個或多個規則匹配,則說明它們之間存在沖突,并使用沖突解決策略來確定執行順序。 Drools戰略非常簡單,基于一個顯著的價值,它為一個規則賦予了優先權。 每個規則的默認值為0,值越高,優先級越高。
作為一般規則,不要指望以任何特定順序開火的規則,并且制定規則而不必擔心“flow”。 然而,當需要流程時,除了顯著性之外還有許多可能性:議程組,規則流程組,激活組和控制/信號量事實。
Drools 6.0 規則根據源文件中的salience之后的數字來確定優先級。
Salience
為了說明salience,我們添加一個規則來打印帳戶余額,我們希望在對所有帳戶應用所有借記和貸項之后執行此規則。 我們通過給這個規則分配一個負的salience來達到這個目的,以便在默認salience 0的所有規則之后觸發。
rule "Print balance for AccountPeriod"salience -50whenap : AccountPeriod()acc : Account()thenSystem.out.println( acc.accountNo + " : " + acc.balance );
end
下表描述了由此產生的議程。 這三個借記和貸記規則顯示為任意順序,而打印規則排在最后,以后執行。

議程組agenda group
議程組允許您將規則放入組中,并將這些組放入堆棧。 該堆棧有push/pop 的能力。 調用“setFocus”將組放入堆棧:
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();
議程總是評估堆棧的頂部。 當所有的規則已經為一個組激發,它從堆棧彈出,并評估下一個組。
rule "increase balance for credits"agenda-group "calculation"
whenap : AccountPeriod()acc : Account( $accountNo : accountNo )CashFlow( type == CREDIT,accountNo == $accountNo,date >= ap.start && <= ap.end,$amount : amount )
thenacc.balance += $amount;
end
rule "Print balance for AccountPeriod"agenda-group "report"
whenap : AccountPeriod()acc : Account()
thenSystem.out.println( acc.accountNo +" : " + acc.balance );
end
首先把重點放在“report”組,然后把重點放在“calculation”上,我們確保首先評估組。
Agenda agenda = ksession.getAgenda();
agenda.getAgendaGroup( "report" ).setFocus();
agenda.getAgendaGroup( "calculation" ).setFocus();
ksession.fireAllRules();
規則流程
Drools還具有ruleflow-group屬性,允許工作流程圖聲明性地指定何時允許激發規則。 下面的截圖是從Eclipse使用Drools插件。 它有兩個規則流組節點,確保計算規則在報告規則之前執行。

在規則中使用ruleflow-group屬性如下所示。
rule "increase balance for credits"ruleflow-group "calculation"
whenap : AccountPeriod()acc : Account( $accountNo : accountNo )CashFlow( type == CREDIT,accountNo == $accountNo,date >= ap.start && <= ap.end,$amount : amount )
thenacc.balance += $amount;
end
rule "Print balance for AccountPeriod"ruleflow-group "report"
whenap : AccountPeriod()acc : Account()
thenSystem.out.println( acc.accountNo +" : " + acc.balance );
end
推理
巴士通行證例子
現在推論有一個不好的名字,因為它與業務用例無關,而且太復雜而無用。 的確,人為的和復雜的例子都是在推論中出現的,但是這也不應該損害簡單有用的存在。 但更重要的是,正確使用推理可以提供更敏捷,更不容易出錯的業務規則,這些規則更容易維護。
那么推理是什么? 當我們通過使用以前的知識獲得某些東西的知識時,就會推斷出什么 例如,給定一個具有年齡字段和規定年齡政策控制的規則的人的事實,我們可以推斷出一個人是成年人還是孩子,并據此采取行動。
rule "Infer Adult"
when$p : Person( age >= 18 )
theninsert( new IsAdult( $p ) ) end
由于前面的規則,每個18歲以上的人都會為他們插入一個IsAdult的實例。 這個事實是特殊的,因為它被稱為關系。 我們可以在任何規則中使用這個推斷關系:
$p : Person()
IsAdult( person == $p )
所以現在我們知道推論是什么,并且有一個基本的例子,這是如何促進良好的規則設計和維護?
讓孩子成年后負責發放身份證的政府部門,以下簡稱ID部門。 他們可能有一個決策表,其中包括這樣的邏輯,它說當一個在倫敦的成年人是18歲或以上,發卡:

但身份證件部門并沒有制定成人的政策。這是在中央政府一級完成的。如果中央政府將這個年齡改為21歲,這將啟動變革管理過程。有人必須聯系身份證件部門,確保他們的系統得到更新,以便法律上線。
這種變更管理流程和部門之間的溝通對于敏捷環境來說并不理想,而且變更成本高昂且容易出錯。此外,信用卡部門正在管理更多的信息,而不是需要通過其“規模管理”的“單一”方法意識到這一點。我的意思是,它并不關心明確的年齡>= 18信息決定某人是否是成年人,而只關心他們是否是成年人。
相比之下,讓我們采取一種方法,將創作責任分開(脫離),使中央政府和身份證部門都保持自己的規則。
確定誰是成年人是中央政府的工作。 如果他們改變法律,他們只是用新的規則來更新他們的中央倉庫,

如前所述,IsAdult事實是從政策規則中推斷出來的。 它封裝了看似隨意的一段邏輯時間?= 18,并為其含義提供了語義抽象。 現在,如果有人使用上述規則,他們不再需要知道明確的信息,決定某人是否是成年人。 他們可以使用推斷的事實:

雖然這個例子是非常微小的,但它說明了一些重要的觀點。我們從知識工程的單一和漏洞入手開始。我們創建了一個包含所有可能的信息的決策表,泄露了ID部門不關心也不想管理的來自中央政府的信息。
我們首先將知識過程分離開來,這樣每個部門只負責知道什么。然后,我們使用推斷的事實IsAdult封裝了這個變動的知識。術語IsAdult的使用還給以前的任意邏輯時間?= 18提供了語義抽象。
所以在進行知識工程時,一般的經驗法則是:
-
壞的
-
單一Monolithic
-
泄露Leaky
-
好的
-
解除對知識的責任
-
封裝知識
-
為這些封裝提供語義抽象
-
用邏輯對象維護真相
概述
定期插入后,你必須明確地收回事實。有了邏輯斷言,斷言的事實將會自動撤回,因為斷言它的條件不再是真實的。實際上,它更加聰明,因為只有當沒有任何單一條件支持邏輯斷言時才會收回。
聲明一個正規的插入,就像“陳述事實”所暗示的直覺意義一樣。使用一個HashMap和一個計數器,我們追蹤一個特定的平等是多少次;這意味著我們計算有多少不同的實例是相等的。
當我們在RHS執行期間邏輯插入一個對象的時候,我們被說成是正當的,并且被認為是通過觸發規則來證明的。對于每個邏輯插入,只能有一個相等的對象,并且每個隨后的相等的邏輯插入增加該邏輯斷言的對齊計數器。創建規則的LHS取消了正當理由,計數器也相應減少。一旦我們沒有更多的理由,邏輯對象自動收回。
如果我們嘗試邏輯插入一個對象時,有一個相等的聲明對象,這將失敗,并返回null。如果我們聲明一個對象具有一個現有的平等的對象,我們重寫事實;這個覆蓋如何工作取決于配置設置WM_BEHAVIOR_PRESERVE。當屬性設置為discard時,我們使用現有的句柄,并用新的對象替換現有的實例,這是默認行為;否則我們重寫它,同時我們創建一個新的FactHandle。
這可能會在第一次閱讀時感到困惑,所以希望下面的流程圖有所幫助。當它說它返回一個新的FactHandle時,這也表示對象是通過網絡傳播的。


有推理和TMS的公共汽車通行證例子
前面的例子是發行身份證超過18歲,在這個例子中,我們現在發行巴士通行證,無論是兒童或成人通行證。
rule "Issue Child Bus Pass" when$p : Person( age < 16 )
theninsert(new ChildBusPass( $p ) ); end rule "Issue Adult Bus Pass" when $p : Person( age >= 16 ) then insert(new AdultBusPass( $p ) ); end
像以前一樣,上面的例子被認為是單一的,漏洞和提供差的關注分離。
像以前一樣,我們可以提供一個更強大的應用程序,使用推理來區分問題。注意這次我們不只是插入推斷的對象,我們使用“insertLogical”:
rule "Infer Child" when$p : Person( age < 16 )
theninsertLogical( new IsChild( $p ) ) end rule "Infer Adult" when $p : Person( age >= 16 ) then insertLogical( new IsAdult( $p ) ) end
“insertLogical”是Drools真相維護系統(TMS)的一部分。當一個事實被邏輯插入時,這個事實取決于“when”從句的真實性。這意味著,當規則成為錯誤時,事實會自動收回。由于這兩個規則是相互排斥的,所以這個效果特別好。所以在上面的規則中,如果這個人在16歲以下,它會插入一個IsChild的事實,一旦這個人是16歲或以上,IsChild的事實就會自動收回,并且插入了IsAdult的事實。
回到代碼發出巴士通行證,這兩個規則可以在邏輯上插入ChildBusPass和AdultBusPass事實,因為TMS +支持鏈接一系列級聯的邏輯插入。
rule "Issue Child Bus Pass" when$p : Person( )IsChild( person == $p )
theninsertLogical(new ChildBusPass( $p ) );
endrule "Issue Adult Bus Pass" when $p : Person( age >= 16 ) IsAdult( person =$p ) then insertLogical(new AdultBusPass( $p ) ); end
現在當一個人從15歲變成16歲時,不僅IsChild事實自動縮回,他的ChildBusPass事實也是如此。對于獎勵積分,我們可以將這個與'not'條件元素結合起來處理通知,在這種情況下,請求返回通行證。所以當TMS自動收回ChildBusPass對象時,這個規則觸發并發送一個請求給這個人:
rule "Return ChildBusPass Request "when$p : Person( )not( ChildBusPass( person == $p ) )
thenrequestChildBusPass( $p );
end
重要說明:Java對象的等價
注意到真理維護(和邏輯斷言)完全可以工作,這是很重要的,你的Fact對象(可能是JavaBeans)必須正確地覆蓋equals和hashCode方法(來自java.lang.Object)。由于事實維護系統需要知道兩個不同物理對象的值是否相等,因此按照Java標準,必須正確覆蓋both等于和hashCode。
兩個對象是相等的,當且僅當它們的equals方法相互返回true,并且它們的hashCode方法返回相同的值。有關更多詳細信息,請參閱Java API(但請記住,MUST必須覆蓋equals和hashCode)。
TMS行為不受標識vs等價的時間配置的影響,TMS始終是等價的。
從工作記憶中刪除陳述或邏輯斷言
默認情況下,當從工作記憶中刪除一個事實時,Drools嘗試從既定事實集合中刪除它,并且在邏輯斷言的情況下也從真值維護系統TMS中刪除它。但是,使用delete方法的重載,也可以只從2.中刪除它。例如,調用:
``ksession.delete( factHandle, FactHandle.State.LOGICAL );``
這個事實只有在邏輯斷言的情況下才會被刪除,但是如果它是一個陳述的事實則不會被刪除。在這種情況下,如果事實已經說明了它的刪除失敗了,并且被忽略了。
電子表格中的決策表
決策表是一種“精確而緊湊”(參考自Wikipedia)表示條件邏輯的方式,非常適合商業級規則。
Drools支持電子表格格式的管理規則。支持的格式是Excel(XLS)和CSV,這意味著可以使用各種電子表格程序(例如Microsoft Excel,OpenOffice.org Calc等)。預計基于網絡的決策表編輯器將被包括在不久的將來版本中。
決策表是一個舊的概念(用軟件來說),但是多年來已經證明是有用的。簡而言之,在Drools中,決策表是一種生成從輸入到電子表格中的數據驅動的規則的方法。數據采集和處理的電子表格的所有常用功能都可以利用。
何時使用決策表
如果存在可以表示為規則模板和數據的規則,則將決策表視為一個行為過程:決策表的每一行都提供與模板結合生成規則的數據。
許多企業已經使用電子表格來管理數據,計算等。如果您樂于繼續這種方式,您也可以通過這種方式來管理您的業務規則。這也假設您很樂于在xls或csv文件中管理規則包。決策表不建議用于不遵循一組模板的規則,也不建議使用少量規則(或者對Excel或OpenOffice.org等軟件不喜歡)。它們是理想的,可以控制規則的參數可以編輯,而不需要直接暴露規則。
決策表還提供了一定程度的基礎對象模型的隔離。
概述
下面是一些真實世界決策表的例子(稍作修改以保護無辜者)。



在上面的例子中,決策表的技術方面已經崩潰了(使用標準的電子表格功能)。
規則從第17行開始,每一行產生一個規則。條件在列C,D,E等中,動作在屏幕外。單元格中的值非常簡單,它們的含義由行16中的標題指示。列B只是一個描述。通常使用顏色來明確表格的不同區域的含義。
? | 請注意,雖然決策表看起來像自上而下,但情況并非如此。理想情況下,規則的編寫不考慮行的順序,僅僅因為這使得維護更容易,因為行不需要一直移動。 |
由于每一行都是一個規則,所以適用相同的原則。由于規則引擎處理事實,任何匹配的規則都可能觸發。 (有些人對此感到困惑,在規則觸發時模擬一個非常簡單的決策表就可以清除議程,只有第一個匹配才會執行一個動作。)另請注意,在一個電子表格中可以有多個表。這樣,可以將規則分組在共享通用模板的位置,然而在一天結束時,它們全部組合成一個規則包。決策表本質上是一種自動生成DRL規則的工具。

決策表如何工作
要記住的關鍵是,決策表中的每一行都是一個規則,該行中的每一列都是該規則的條件或動作。

電子表格查找RuleTable關鍵字以指示規則表(起始行和列)的開始。 其他關鍵字也用于定義其他包級別屬性(稍后介紹)。 將關鍵字保留在一列是很重要的。 按照慣例,第二列(“B”)用于這個,但它可以是任何列(約定左邊留有空白的備注)。 在下面的圖中,C實際上是它開始的列。 左邊的所有內容都被忽略。
如果我們擴大隱藏的部分,它開始變得更有意義它是如何工作的; 請注意C列中的關鍵字。

現在可以看到使其工作的隱藏的魔法。 RuleSet關鍵字指示將在rule package中使用的名稱,該名稱將包含所有規則。 此名稱是可選的,使用默認值,但必須在單元格右邊有RuleSet關鍵字。
列C中可見的其他關鍵字是“導入”和“順序”,稍后將對其進行介紹。 RuleTable關鍵字是很重要的,因為它表示將遵循一些規則,基于一些規則模板。 RuleTable關鍵字后面有一個名稱,用于為生成的規則的名稱添加前綴。 附加表單名稱和行號以保證唯一的規則名稱。
? | 與表格名稱結合使用的表格名稱在同一KieBase中的所有電子表格文件中必須是唯一的。 如果不是這樣,一些規則可能會有相同的名稱,只有其中一個會被應用。 要顯示這樣被忽略的規則,https://docs.jboss.org/drools/release/7.4.1.Final/drools-docs/html_single/index.html#_changingthedefaultbuildresultresultseverity [引發此類規則名稱沖突的嚴重性]。 |
RuleTable的列表示規則開始的列; 左側的列將被忽略。
? | 通常,關鍵字組成名稱 - 值對。 |
參考第14行(RuleTable之后的那一行),關鍵字CONDITION和ACTION表示下面各列中的數據適用于規則的LHS部分或RHS部分。 規則上還有其他屬性,也可以選擇這種方式設置。
第15行包含ObjectTypes的聲明。 該行中的內容是可選的,但是如果該選項未被使用,則該行必須留空; 然而這個選項通常被認為是相當有用的。 使用此行時,下面單元格(第16行)中的值將成為該對象類型的約束。 在上面的例子中,它生成了“Person(age ==”42“)”和“Cheese(type ==”stilton“)”,其中42和“stilton”來自第18行。 ,“==”是隱含的; 如果只給出一個字段名稱,翻譯者就會假定它是生成完全匹配的。
? | 一個ObjectType聲明可以跨越列(通過合并的單元格),這意味著合并范圍以下的所有列將被合并到一個模式中,一次匹配一個事實的一個模式中,而非包含 相同的ObjectType,但導致不同的模式,可能匹配不同或相同的事實。 |
第16行包含規則模板本身。 他們可以使用“$ param”占位符來表示下面單元格的數據應該插入的位置。 (對于多次插入,請使用“$ 1”,“$ 2”等,表示下面單元格中逗號分隔列表中的參數。 它可能包含該欄目的文字說明。
行18和行19顯示的數據將與行15中的模板組合(插值),以生成規則。 如果一個單元格不包含數據,則其模板將被忽略。 (這意味著某些條件或操作不適用于該規則行。)讀取規則行直到出現空行。 一張表中可以存在多個RuleTables。 第20行包含另一個關鍵字和一個值。 像這樣的關鍵字的行位置并不重要(大多數人把它們放在頂部),但是它們的列應該與RuleTable或RuleSet關鍵字出現的位置相同。 在我們的情況下,列C被選擇為重要的,但是可以使用任何其他列。
在上面的例子中,規則會像下面一樣呈現(因為它使用“ObjectType”行):
//row 18
rule "Cheese_fans_18"
whenPerson(age=="42") Cheese(type=="stilton") then list.add("Old man stilton"); end
? | “age ==”42“”和“type ==”stilton“”的約束被解釋為單個約束,被添加到上面單元格的相應ObjectType中。 如果上面的單元格是跨越的,那么在一個“列”上可能有多個約束。 |
? | 非常大的決策表可能有非常大的內存要求。 |
電子表格語法
電子表格結構
有兩種類型的矩形區域定義用于生成DRL文件的數據。 一個由標記為“RuleSet”的單元標記,定義除規則以外的所有DRL項目。 另一個可能會重復發生,并且位于以“RuleTable”開頭的單元格的右下方。 這些領域代表了實際的決策表,每個領域產生了一套類似結構的規則。
一個規則集區域可以包含單元對,一個在RuleSet下面,包含一個關鍵字,指定在同一行中的另一個關鍵字中包含的值。
規則表區域的列定義了從中派生的規則左側的模式和約束,規則后果的動作以及單個規則屬性的值。因此,規則表區域應該包含一個或多個列,條件和操作,以及規則屬性的列的任意選擇,每個列最多一列。前面四行跟著標記有“RuleTable”單元格的行被標記為標題區域,主要用于定義構建規則的代碼。它是這四個標題行下面的任何額外的行,產生另一個規則,其數據提供規則表標題中定義的代碼的變化。
所有關鍵字不區分大小寫。
只有第一張工作表被檢查決策表。
規則集條目
規則集區域中的條目可以定義DRL構造(規則除外),并指定規則屬性。 盡管可以重復使用結構的條目,但是每個規則屬性最多只能給出一次,并且它適用于所有規則,除非它被“規則表”區域內定義的相同屬性取代。
條目必須以垂直堆疊的單元對序列給出。 第一個包含關鍵字和右邊的值,如下表所示。 只要由“RuleSet”標記的列被維護為包含關鍵字的那一列,這個單元格對的序列就可以被空行或甚至規則表中斷。
關鍵字 | 值 | 用法 |
RuleSet | 生成的DRL文件的包名稱。可選,默認是 | 必須是第一個入口。 |
Sequential | "true" or "false". 如果是“true”,則使用顯著性來確保規則從上到下起火。 | 可選,最多一次。如果省略,則不執行射擊命令。 |
SequentialMaxPriority | 整數數值 | 可選,最多一次。在順序模式下,此選項用于設置突出顯示的起始值。如果省略,則默認值為65535。 |
SequentialMinPriority | 整數數值 | 可選,最多一次。在順序模式下,此選項用于檢查是否違反了最小顯著性值。如果省略,則默認值為0。 |
EscapeQuotes | "true" or "false". 如果是“true”,那么引號就會被轉義,從而在DRL中出現。 | 可選,最多一次。如果省略,引號將被轉義。 |
NumericDisabled | "true" or "false". 如果是“true”,則字符串表示形式用于DRL,而不是來自Numeric單元格的double值可選,最多一次。如果省略,則使用double值。 | Import |
要導入的Java類的逗號分隔列表。 | 可選,可以重復使用。 | 變量 |
DRL全局變量的聲明,即一個后跟一個變量名的類型。多個全局定義必須用逗號分隔。 | 可選,可以重復使用。 | Variables |
DRL全局變量的聲明,即一個后跟一個變量名的類型。多個全局定義必須用逗號分隔。 | 可選,可以重復使用。 | Functions |
一個或多個函數定義,根據DRL語法。 | 可選,可以重復使用。 | Queries |
一個或多個查詢定義,根據DRL語法。 | 可選,可以重復使用。 | Declare |
在某些語言環境中,MS Office,LibreOffice和OpenOffice會對不同的雙引號進行編碼,這會導致編譯錯誤,通常很難看出差異,例如: |
? | 要定義適用于生成的DRL文件中所有規則的規則屬性,可以使用下表中的任何條目。 但是請注意,必須使用正確的關鍵字。 而且,每個屬性只能使用一次。 |
Keyword | Initial | Value |
PRIORITY | P | 一個定義規則“顯著性”值的整數。由“順序”標志覆蓋。 |
DURATION | D | 定義規則的“持續時間”值的長整數值。 |
TIMER | T | 定時器定義。請參閱“定時器和日歷”。 |
ENABLED | B | 一個布爾值。 “真”使規則成為可能; “false”會禁用規則。 |
CALENDARS | E | 日歷定義。請參閱“定時器和日歷”。 |
NO-LOOP | U | 一個布爾值。 “真”禁止由于其結果所做的更改而循環的規則。 |
LOCK-ON-ACTIVE | L | 一個布爾值。 “true”禁止在同一個規則流或議程組中設置此標志的所有規則的額外激活。 |
AUTO-FOCUS | F | 一個布爾值。對于議程組中的規則而言,“真實”會導致規則的激活,從而自動將焦點集中到組中。 |
ACTIVATION-GROUP | X | 識別激活(或XOR)組的字符串。激活組中只有一個規則將被觸發,即第一個激活組將取消同一組內其他規則的任何激活。 |
AGENDA-GROUP | G | 一個標識一個議程組的字符串,必須通過賦予其“焦點”來激活,這是控制規則組之間流動的一種方式。 |
RULEFLOW-GROUP | R | 標識規則流組的字符串。 |
規則表
所有規則表都以一個包含“RuleTable”的單元格開始,可選地在同一個單元格中跟隨一個字符串。該字符串用作從該規則表派生的所有規則的名稱的起始部分,附加行號以區分。 (這個自動命名可以通過使用NAME列來覆蓋。)定義此Rule Table規則的所有其他單元格位于該單元格的下方和右側。
下一行定義了列的類型,每列產生一部分條件或結果,或者提供一些規則屬性,規則名稱或注釋。下表顯示了哪些列標題可用;根據顯示前一節中給出的規則屬性條目的表格,可以使用額外的列。請注意,每個屬性列最多只能使用一次。對于列標題,請使用關鍵字或任何其他以這些表格的“初始”列中給出的字母開頭的單詞。
Keyword | Initial | Value | Usage |
NAME | N | 提供從該行生成的規則的名稱。 默認值是根據RuleTable標簽和行號之后的文本構造的。 | 最多只有一列 |
DESCRIPTION | I | 一個文本,在生成的規則中產生一個注釋。 | 最多只有一列 |
CONDITION | C | 代碼片段和插值,用于在條件中的模式中構建約束。 | 每個規則表至少有一個 |
ACTION | A | 代碼片斷和插值用于構建規則后果的操作。 | 每個規則表至少有一個 |
METADATA | @ | 代碼片斷和插值用于構建規則的元數據條目。 | 可選,任意數量的列 |
給定一個標題為CONDITION的列,連續行中的單元格產生一個條件元素。
-
CONDITION下面的第一個單元格中的文本發展成規則條件的模式,下一行中的代碼段成為約束條件。如果單元格與一個或多個鄰居合并,則形成具有多個約束的單個模式:將所有約束合并到一個帶括號的列表中,并附加到該單元格中的文本。單元格可能會留空,這意味著下一行中的代碼片段必須單獨生成有效的條件元素。 要包含沒有約束的模式,可以將模式寫在另一個模式的文本前面。 模式可以寫有或沒有一個空的括號。 “from”子句可以附加到模式。 如果模式以“eval”結尾,代碼片段應該產生布爾表達式,以包含在“eval”之后的一對括號中。
-
CONDITION下面的第二個單元格中的文本分兩步處理。
-
這個單元格中的代碼片段是通過插入列中更靠下的單元格中的值來修改的。如果要使用“==”創建一個由下面的單元格的值組成的約束條件,則僅字段選擇器就足夠了。任何其他比較運算符都必須被指定為代碼片段中的最后一項,并且下面單元格的值被附加。對于所有其他約束形式,您必須標記位置以包含符號“$ param”的單元格的內容。通過在下面的單元格中使用符號“$ 1”,“$ 2”等和逗號分隔值列表,可以實現多重插入。 根據模式
forall(
的文本的文本通過對每個單元格中的逗號分隔值列表的每個值重復snippet來展開在下面插入值代替符號delimiter
){snippet
}$
并且通過給定的delimiter加入這些擴展。請注意,該構造可能被其他文本包圍。 -
如果前一行中的單元格不是空的,則將完成的代碼片段添加到該單元格的條件元素中。一對括號是自動提供的,如果將多個約束添加到合并單元格中的模式,則會自動提供分隔逗號。 如果上面的單元格是空的,插入的結果將按原樣使用。
-
-
CONDITION下方的第三個單元格中的文本僅用于文檔。它應該用來向讀者指出專欄的目的。
-
從第四行開始,非空白條目提供如上所述的插值數據。空白單元格導致省略該規則的條件元素或約束條件。 給出一個以行動為首的專欄,連續行中的單元格產生一個行動陳述。
-
ACTION下面的第一個單元格中的文本是可選的。如果存在,則將其解釋為對象引用。
-
ACTION下面第二個單元格中的文本分兩步處理。
-
這個單元格中的代碼片段是通過插入列中更靠下的單元格中的值來修改的。對于單數插入,用符號“$ param”標記包含單元格內容的位置。通過在下面的單元格中使用符號“$ 1”,“$ 2”等和逗號分隔值列表,可以實現多重插入。 沒有內插的方法調用可以通過沒有任何標記符號的文本來實現。在這種情況下,請使用下面一行中的任何非空白條目來包含該語句。 這個構造也可以在這里找到。 2.如果第一個單元格不是空的,其文本,后面是一個句點,第二個單元格中的文本和終止分號被串聯在一起,產生一個方法調用,作為結果的一個動作語句添加。 如果上面的單元格是空的,插入的結果將按原樣使用。
-
-
ACTION下面的第三個單元格中的文本僅用于文檔。它應該用來向讀者指出專欄的目的。
-
從第四行開始,非空白條目提供如上所述的插值數據。空白單元格導致省略此規則的操作語句。
? | 在大多數情況下,使用“$ 1”而不是“$ param”工作,但如果替換文本包含逗號,則會失敗:然后,只插入第一個逗號前面的部分。 明智地使用這個“縮寫”。 |
給定一個以METADATA開頭的列,連續行中的單元格將為生成的規則生成元數據注釋。
-
METADATA下面第一個單元格中的文本被忽略。
-
如上所述,METADATA下面的第二個單元格中的文本使用來自規則行單元格的值進行插值。元數據標記字符“@”是自動添加的,因此它不應該包含在這個單元格的文本中。
-
METADATA下面的第三個單元格中的文本僅用于文檔。它應該用來向讀者指出專欄的目的。
-
從第四行開始,非空白條目提供如上所述的插值數據。空白單元格導致省略此規則的元數據注釋。
例子
以下示例說明了各種插值。 例85.插值單元格數據 如果模板是“Foo(bar == $ param)”,單元格是“42”,那么結果是“Foo(bar == 42)”。
如果模板是“Foo(bar <$ 1,baz == $ 2)”,而單元格包含“42,43”,則結果將是“Foo(bar <42,baz == 43) 。
模板forall(&&){bar!= $}
與一個包含42,43
的單元格產生bar!= 42 && bar!= 43
。
下一個例子演示了定義模式類型的單元和下面的代碼片段的共同作用。

<br class="Apple-interchange-newline">296/5000該電子表格部分顯示了Person類型聲明如何跨越2列,因此這兩個約束將以Person(age == …?,type == …?)的形式出現。 由于只有字段名稱存在于片段中,因此意味著平等測試。
在下面的例子中使用了標志符號$ param。

這個列的結果是模式Person(age ==“42”))。 您可能已經注意到標記和運算符“==”是多余的。
下一個例子說明了一個尾隨的插入標記可以省略。

在這里,從單元格中附加值是隱含的,導致Person(年齡<42))。
您可以提供綁定變量的定義,如下例所示。

這里的結果是c:Cheese(type ==“stilton”)。 請注意,報價是自動提供的。 實際上,任何東西都可以放在對象類型的行中。 除了綁定變量的定義之外,它也可以是一個附加的模式,可以直接插入。
下面顯示了插入單個值的操作語句的簡單構造。

ACTION標題下面的單元格留空。 使用這種風格,任何事情都可以放在結果中,而不僅僅是一個方法調用。 (同樣的技術也適用于CONDITION列。)
下面是一個綜合的例子,展示了各種列標題的使用。 在列標題之下沒有任何值是錯誤的(如在NO-LOOP列中):在這里,屬性不會被應用在任何規則中。

最后,這里是一個導入,變量和函數的例子。

同一單元格中的多個軟件包名稱必須用逗號分隔。此外,類型和變量名稱對必須用逗號分隔。但是,函數必須按照出現在DRL文件中的方式編寫。這應該與“RuleSet”關鍵字出現在同一列;它可以在所有規則行之上,之間或之下。
? | It may be more convenient to use Import, Variables, Functions and Queries repeatedly rather than packing several definitions into a single cell. |
創建和集成基于電子表格的決策表
使用基于電子表格的決策表的API位于drools-decisiontables模塊中。實際上只有一個類可以看:SpreadsheetCompiler。這個類將采取各種格式的電子表格,并在DRL中生成規則(然后可以以正常的方式使用)。 SpreadsheetCompiler可以用來生成部分規則文件(如果需要的話),然后在事實之后將其組裝成一個完整的規則包(如果需要,可以將規則的技術和非技術方面分開)。
要開始,可以使用示例電子表格作為基礎。或者,如果正在使用插件(Rule Workbench IDE),則向導可以從模板生成電子表格(編輯它需要使用xls兼容的電子表格編輯器)。

管理決策表中的業務規則
工作流程和協作
電子表格是完善的商業工具(使用了25年以上)。決策表適合密切IT和領域專家之間的合作,同時使業務分析人員明確業務規則,這是一個理想的分離關注點。
通常,編寫規則的整個過程(提出一個新的決策表)將是這樣的:
-
業務分析師采用模板決策表(從存儲庫或從IT)
-
決策表業務語言描述被輸入到表格中
-
決策表規則(行)被輸入(大致)
-
決策表交給技術資源,他將業務語言(描述)映射到腳本(當然,這可能涉及軟件開發,如果是新的應用程序或數據模型)
-
技術人員交回業務分析師的修改。
-
業務分析師可以根據需要繼續編輯規則行(移動列周圍也很好等)。
-
同時,技術人員可以為規則開發測試用例(與業務分析人員聯系),因為一旦系統運行,這些測試用例可用于驗證規則和規則更改。
使用電子表格功能
Excel等應用程序的功能可用于幫助將數據輸入電子表格,例如驗證字段。存儲在其他工作表中的列表可用于為單元格提供有效的值列表,如下圖所示。

一些應用程序提供了有限的能力來保存更改的歷史記錄,但是建議使用另一種版本控制方法。當規則隨時間發生變化時,舊版本將被歸檔(許多開源解決方案,例如Subversion或Git)。
規則模板
與決策表(但不一定需要電子表格)相關的是“規則模板”(在drools-templates模塊中)。這些使用任何表格數據源作為規則數據的來源 - 填充模板以生成許多規則。這既可以用于更靈活的電子表格,也可以用于現有數據庫中的規則(以開發模板為代價來生成規則)。
使用規則模板,數據與規則是分開的,并且規則的哪一部分是數據驅動的沒有限制。所以盡管你可以在決策表中做所有事情,但你也可以做以下的事情:
-
將數據存儲在數據庫(或其他格式)
-
有條件地根據數據中的值生成規則
-
為規則的任何部分使用數據(例如條件運算符,類名稱,屬性名稱)
-
通過相同的數據運行不同的模板。例如,顯示了一個更經典的決策表,但沒有規則元數據的任何隱藏行(所以電子表格只包含生成規則的原始數據)。

請參閱上述電子表格示例下載中的ExampleCheese.xls。
如果這是一個常規決策表,那么在行1之前以及包含規則元數據的行1和行2之間將存在隱藏行。使用規則模板,數據與規則完全分開。這有兩個方便的后果 - 您可以將多個規則模板應用于相同的數據,并且您的數據根本不依賴于您的規則。那么模板是什么樣的?
1 template header
2 age
3 type
4 log
5 6 package org.drools.examples.templates; 7 8 global java.util.List list; 9 10 template "cheesefans" 11 12 rule "Cheese fans_@{row.rowNumber}" 13 when 14 Person(age == @{age}) 15 Cheese(type == "@{type}") 16 then 17 list.add("@{log}"); 18 end 19 20 end template
上述程序清單的注釋:
-
第1行:所有規則模板以
template header
開始。 -
第2-4行:在標題之后是按照它們在數據中出現的順序排列的列表。在這種情況下,我們稱第一列時間,第二種類型和第三個日志。
-
第5行:空行表示列定義的結束。
-
第6-9行:標準規則標題文本。這是DRL的標準規則,將出現在生成的DRL的頂部。將package語句和任何導入以及全局和函數定義放入本節。
-
第10行:關鍵字模板表示規則模板的開始。模板文件中可以有多個模板,但每個模板都應該有唯一的名稱。
-
第11-18行:規則模板 - 詳見下文。
-
第20行:關鍵字結束模板表示模板的結尾。
規則模板依靠MVEL使用語法@ {token_name}進行替換。目前有一個內置的表達式:@ {row.rowNumber},它為每一行數據提供唯一的編號,并使您能夠生成唯一的規則名稱。對于每一行數據,都會生成一個規則,用數據中的值代替模板中的令牌。
規則模板必須包含在擴展名為.drt的文件中,并且在定義kmodule.xml文件中的kbase時與相應的決策表相關聯,如下例所示
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://drools.org/xsd/kmodule"><kbase name="TemplatesKB" packages="org.drools.examples.templates"> <ruleTemplate dtable="org/drools/examples/templates/ExampleCheese.xls" template="org/drools/examples/templates/Cheese.drt" row="2" col="2"/> <ksession name="TemplatesKS"/> </kbase> </kmodule>
通過上面的示例數據,將生成以下規則文件:
package org.drools.examples.templates;global java.util.List list;rule "Cheese fans_1"
whenPerson(age == 42) Cheese(type == "stilton") then list.add("Old man stilton"); end rule "Cheese fans_2" when Person(age == 21) Cheese(type == "cheddar") then list.add("Young man cheddar"); end
此時,名為“TemplatesKS”的KieSession包含從模板生成的規則,可以簡單地從KieContainer創建,并用作其他KieSession。
KieSession ksession = kc.newKieSession( "TemplatesKS" );// now create some test data
ksession.insert( new Cheese( "stilton", 42 ) ); ksession.insert( new Person( "michael", "stilton", 42 ) ); final List<String> list = new ArrayList<String>(); ksession.setGlobal( "list", list ); ksession.fireAllRules();
記錄日志
照亮作為規則引擎的黑盒子的一種方法是玩日志級別。
一切都記錄到SLF4J,這是一個簡單的日志記錄門面,可以將任何日志委托給Logback,Apache Commons Logging,Log4j或java.util.logging。將日志適配器的依賴關系添加到您選擇的日志框架中。如果你還沒有使用任何日志框架,你可以通過添加這個Maven依賴項來使用Logback:
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.x</version></dependency>
? | 如果您正在開發超輕型環境,請使用slf4j-nop或slf4j-simple。 |
在包org.drools上配置日志記錄級別。例如:
在Logback中,將其配置到您的logback.xml文件中:
<configuration><logger name="org.drools" level="debug"/>...<configuration>
在Log4J中,將其配置到您的log4j.xml文件中:
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"><category name="org.drools"><priority value="debug" /></category>...</log4j:configuration>