內容提要
在本文的第一部分,我將討論規則引擎如何幫助你從軟件的應用邏輯中分離出商業規則邏輯,以實現商業應用的靈活性。另外,我還將介紹JSR-94規則引擎
API,及其開源實現Drools項目,它是這一新技術的先驅。在第二部分,我們將介紹一個規則引擎例子,并深入地研究Drools引擎及其JSR-94
擴展的復雜性。
為什么使用規則引擎
商業世界充滿了關于變化的陳詞濫調,如任何事物都會改變,唯一不變的是變化等等。而在技術領域里,情況正好相反。我們仍然在試圖解決30年前軟件業中同樣
的一堆問題--也許比30年前還要多的問題。在過去的十年,IT從業人員淹沒在軟件方法學的大量文獻中,如快速軟件開發,極限編程,敏捷軟件開發等,它們
無一例外地強調靈活和變化的重要性。
但商業通常比開發團隊所依賴的軟件過程和技術改變得更加迅速。當商業策劃人員試圖重整IT部門,以支持新的業務轉型時,仍然覺得很費勁。
Lost in Translation
雖然IT團隊反應迅速,但他們通常帶來"電話效應"――IT給商業計劃的執行帶來的阻力和它帶來的利益一樣多。不幸的是,在開發團隊完全理解商業決策規則
并實現之前,規則已經改變了。在軟件進入市場前,它已經過時了,需要進行重構以滿足新的業務需求。如果你是一個開發人員,你會知道我在說什么。再也沒有比
在需求變動的情況下構造軟件讓開發人員更沮喪的事情了。作為軟件開發人員,你必須比業務人員更了解業務,有時還要了解更多。
試想一下你是一位商業決策者。假如公司的成功依賴于你對于市場趨勢敏銳的洞察力,它常常幫助你領先于競爭者利用變化的市場環境獲利。每天你都會得到更多更
好的市場信息,但并不要緊。完成新產品開發可能需要6-9個月,在此期間,對于市場大膽和敏銳的洞察和信息優勢可能已經浪費了。而且,當產品發布時,有這
樣幾種可能:產品沒有什么吸引人的特性,預算超支,過了產品的最佳發布期限,或三者兼而有之。
情況可能還會更糟,在完成產品開發時,市場環境和規劃產品開發時相比,已經發生了根本變化。現在你必須要遵守新的規則,你已經喪失了你的邊際優勢,而且設
計軟件的五人中的三人已經離開了公司。你必須給接手的新人重新講解復雜的業務。如果事情不順利,你可能發現自己要對付一個缺少文檔,并且你完全不了解的遺
留應用。
你的戰略在哪出現了問題?你在哪里應該可以做到更好?最近的輕量級軟件過程,如極限編程,敏捷軟件開發等都在強調自動單元測試和軟件功能優先級的重要性。
除此之外,還有其他的原則,你的開發團隊可能也很熟悉,這些原則可以幫助他們對需求的變動作出迅速反應并縮短項目的開發周期。這些原則的大多數,如系統分
解,多年前就已經出現,并得到了Java平臺的支持(如JMX等),還有如面向對象和角色建模,已經內建在Java語言中。
但Java仍然是一門相當年輕的語言,而且Java平臺遠遠還沒有完備。當前在Java社區,一個引人注目的新技術是,分離商業決策者的商業決策邏輯和應
用開發者的技術決策,并把這些商業決策放在中心數據庫,讓它們能在運行時(即商務時間)可以動態地管理和修改。這是一個你值得考慮的策略。
為什么你的開發團隊不得不象商業經理人一樣,在代碼中包含復雜微妙的商業決策邏輯呢?你怎樣才能向他們解釋決策推理的微妙之處呢?你這樣做是否謹慎呢?可
能不是。象bottom
line一樣,某些東西在解釋的過程中丟失了。為什么要冒這樣的風險,讓應用代碼或測試代碼錯誤地表達你的商業決策邏輯呢?如果這樣做的話,你怎樣檢查它
們的正確性呢――難道你自己想學習如何編程和編寫測試代碼,或者你的客戶會為你測試軟件?你一方面要應付市場,一方面要應付軟件代碼,這實在太困難了。
如果能將這些商業決策規則集中地放在一個地方,以一種你可以理解的格式定義,讓你可以直接管理,而不是散落在代碼的各個角落,那該有多好。如果你能把商業
決策規則獨立于你的軟件代碼,讓開發團隊作出技術決策,你將會獲得更多好處。你的項目開發周期會更短,軟件對于變動的需求更靈活。
規則引擎標準Java API
2003年11月,Java社區通過了Java Rule Engine
API規范(JSR-94)的最后草案。這個新的API讓開發人員在運行時訪問和執行規則有了統一的標準方式。隨著新規范產品實現的成熟和推向市場,開發
團隊將可以從應用代碼中抽取出商業決策邏輯。
這就需要新一代的管理工具,幫助商務經理人可以定義和細化軟件系統的行為。不必通過開發過程來修改應用,并假定可以得到正確的結果,經理人將可以隨時根據需要修改決策規則,并進行測試。
但這將需要開發人員在設計系統時作出某些改變,并可以得到合適的開發工具。
分離商務和技術的關注點
這是一個非常簡單的例子,從經理人的角度,說明如何分離商務和技術的關注點。
你管理著一個反向投資基金。你公司計算機系統的一部分用于分析股票價格,收益和每股凈資產,并在需要時向你提出預警。這個計算機系統的工作是,識別出PE比率比市場平均值低的股票,并標記出來以便進一步的檢查。
你的IT部門擁有一大堆數據,并開發了一系列你可以在規則中引用的簡單數據對象。現在,為簡單起見,假設你是一名受過良好教育的,了解技術的管理人,你了解XML的基本知識,可以讓你編寫和修改簡單的XML規則文件。
你的第一個規則是,給道瓊斯所有的股票估值,并剔除P/E比率大于10的股票(這有點過分簡化,但這里只作為一個例子)。保留下來的股票用來生產一系列報表。對于這個簡單的例子,你的規則文件看起來如下(我們將會過頭來討論這個文件的結構):
<stock:overvalued>
<stock:index> DJIA </stock:index>
<stock:pe> over 10.0 </stock:pe>
</stock:overvalued>
一個月后,你接到一家巴西分析師公司的電話,雇傭你的公司生成一系列巴西股市的報表,但他們有更嚴格的標準。而目前在巴西,P/E比率市場平均值是個位
數,因此你用來評估被市場低股票的閾值需要改變。除了較低的P/E比率,你的新客戶還要求以Price-to-Book比率作為參考標準。
你啟動規則編輯器,并修改規則以匹配新的評估條件。現在,規則引擎剔除巴西股市中P/E比率大于6.5,以及Price to Book 比率小于等于1的股票。完成規則文件修改后,看起來如下:
<stock:overvalued>
<stock:index> Brazil </stock:index>
<stock:pe> over 6.5 </stock:pe>
<stock:pb> over 1.0 </stock:pb>
</stock:overvalued>
你無需為此向開發團隊作任何解釋。你無需等待他們開發或測試程序。如果你的規則引擎的語義足夠強大,讓你描述工作數據,你可以隨時按需修改商業規則。
如果限制因素是規則的定義語言和數據模型,你可以確信這兩者將會標準化,并出現先進的編輯器和工具,以簡化規則的定義,保存和維護。
現在,我希望你已經清楚以下的原則:在這個例子中,哪只股票是否被選擇是一個商務決策,而不是技術決策。決定將哪只股票交給你的分析師是經理人的邏輯
――"logic of the bottom
line"。經理人作出這些決策,并可以按需定制應用。這些規則因此變成了一種控制界面,一種新的商業系統用戶界面。
使用Rule開發
如果在這個應用場景中,你是一個開發人員,你的工作會稍微輕松一些。一旦你擁有了一種用于分析股票的規則語言,你可以取出數據對象并交給規則引擎執行。我們將會到規則語言的討論,但現在我們繼續剛才的例子。
你的系統將一系列的stock
bean輸入規則引擎。當規則執行后,你可以選出符合條件的股票并可以對它們作進一步處理。也許是把它們輸入報表生成系統。分析師使用這些報表幫助他們分
析股市。同時,老板也可能讓你使用新的技術分析工具,并用Dow理論預測股市的底部和頂部。
規則引擎可以讓你的系統變得更簡單,因為你無需在代碼中編寫商務邏輯,如怎樣選擇股票,選擇股票過程中奇怪的條件組合等。這些邏輯不再進入你的代碼。你將可以專注于數據模型。
現在可以這么認為,通過從應用代碼中剝離出易變的商業邏輯,你的效率會更高。但凡是總有例外――簡單應用可能并不能從規則系統中獲益。但如果你開發一個大型系統,有很多易變的商業邏輯,你可以考慮在應用中集成規則引擎。
除了從應用代碼中剝離出商業決策邏輯外,規則引擎還有其他用處。有時候你需要應用成百上千的規則進行決策,并且有上千個對象和這些規則一起使用。很難想象
有什么先進的人工智能引擎可以處理這種情況。遇到這種情況,你需要一個極快的決策算法或是大型機。大型機并不便宜,但你可以非常便宜的得到效率和可伸縮性
最好的算法。
Bob McWhirter的Drools項目
現在,我要介紹Drools項目,Charles Forgy Rete算法的一個增強的Java語言實現。Drools是一個Bob
McWhirter開發的開源項目,放在The
Codehaus上。在我寫這篇文章時,Drools發表了2.0-beata-14版。在CVS中,已完整地實現了JSR94 Rule
Engine API并提供了單元測試代碼。
Rete算法是Charles
Forgy在1979年發明的,是目前用于生產系統的效率最高的算法(除了私有的Rete
II)。Rete是唯一的,效率與執行規則數目無關的決策支持算法。For the uninitiated, that means it can
scale to incorporate and execute hundreds of thousands of rules in a
manner which is an order of magnitude more efficient then the next best
algorithm。Rete應用于生產系統已經有很多年了,但在Java開源軟件中并沒有得到廣泛應用(討論Rete算法的文檔參見
http://herzberg.ca.sandia.gov/jess/docs/61/rete.html。)。
除了應用了Rete核心算法,開源軟件License和100%的Java實現之外,Drools還提供了很多有用的特性。其中包括實現了JSR94
API和創新的規則語義系統,這個語義系統可用來編寫描述規則的語言。目前,Drools提供了三種語義模塊――Python模塊,Java模塊和
Groovy模塊。本文余下部分集中討論JSR94 API,我將在第二篇文章中討論語義系統。
作為使用javax.rules
API的開發人員,你的目標是構造一個RuleExecutionSet對象,并在運行時通過它獲得一個RuleSession對象。為了簡化這個過程,
我編寫了一個規則引擎API的fa?ade,可以用來解釋代表Drools的DRL文件的InputStream,并構造一個
RuleExecutionSet對象。
在上面提到了Drools的三種語義模塊,我接下來使用它們重新編寫上面的例子XML規則文件。這個例子中我選擇Java模塊。使用Java模塊重新編寫的規則文件如下:
<rule name="FlagAsUndervalued">
<parameter identifier="stock">
<java:class>org.codehaus.drools.example.Stock</java:class>
</parameter>
<java:condition>stock.getIndexName().equals("DJIA");</java:condition>
<java:condition>stock.getPE() > 10 </java:condition>
<java:consequence>
removeObject(stock); ( 譯注:應該是retractObject(stock) )
</java:consequence>
</rule>
</rule-set>
現在的規則文件并沒有上面的簡潔明了。別擔心,我們將在下一篇文章討論語義模塊。現在,請注意觀察XML文件的結構。其中一個rule-set元素包含了
一個或多個rule元素,rule元素又包含了parameter,condition和consequence元素。Condition和
consequence元素包含的內容和Java很象。注意,在這些元素中,有些事你可以做,有些事你不能做。目前,Drools使用
BeanShell2.0b1作為它的Java解釋器。我在這里并不想詳細的討論DRL文件和Java語義模塊的語法。我們的目標是解釋如何使用
Drools的JSR94 API。
在Drools項目CVS的drools-jsr94模塊中,單元測試代碼包含了一個ExampleRuleEngineFacade對象,它基于
Brian Topping的Dentaku項目。這個fa?ade對象通過javax.rules
API,創建了供RuleExecutionSet和RuleSession使用的一系列對象。它并沒有完全包括了Drools引擎API的所有特性和細
微差別,但可以作為新手使用API的一個簡單例子。
下面的代碼片斷顯示如何使用規則引擎的facade構造一個RuleExecutionSet對象,并通過它獲得一個RuleSession對象。
import java.io.InputStream;
import javax.rules.*;
import org.drools.jsr94.rules.ExampleRuleEngineFacade;
public class Example {
private ExampleRuleEngineFacade engine;
private StatelessRuleSession statelessSession;
/* place the rule file in the same package as this class */
private String bindUri = "myRuleFile.drl"
public Example() {
/* get your engine facade */
engine = new ExampleRuleEngineFacade();
/* get your input stream */
InputStream inputStream =
Example.class.getResourceAsStream(bindUri);
/* build a RuleExecutionSet to the engine */
engine.addRuleExecutionSet(bindUri, inputStream);
/* don't forget to close your InputStream! */
inputStream.close();
/* get your runtime session */
this.statelessSession = engine.getStatelessRuleSession(bindUri);
}
...
}
在以上的例子代碼中,你需要處理InputStream的IOException例外,這里為了簡單起見省略了。你要做的只是構建InputStream
對象,并把它輸入ExampleRuleEngineFacade,用來創建一個RuleExecutionSet對象。然后,你可以得到一個
StatelessRuleSession,并用它來執行所有的規則。使用StatelessRuleSession相對簡單。我們可以給上面的類添加一
個方法,用來對一個對象列表執行規則:
public List getUndervalued(List stocks) {
return statelessSession.executeRules(stocks);
}
該方法輸入一個stock對象列表給規則引擎,然后使用規則評估輸入的股票對象,并剔除那些不符合價值低估標準的股票。它是個簡單的例子,但足以說明問題。
在ExampleRuleEngineFacade類中,代碼會稍微有些復雜。ExampleRuleEngineFacade類創建了一個
RuleServiceProvider對象,并用它創建RuleAdministrator,RuleExecutionSetProvider和
RuleRuntime對象。RuleExecutionSetProvider負責解釋InputStream,并創建一個
RuleExecutionSet對象。RuleRuntime對象用來得到一個session,RuleAdministrator用來管理所有的對
象。在往下是Drools核心API,它的核心是Rete算法實現。我在這里不打算詳細討論,但你可以看看
ExampleRuleEngineFacade的代碼。
現在你已經看到了在商業和科研方面使用規則引擎的一些例子,并對Drools項目有了基本的了解。在下一篇文章里,我將討論DRL文件的結構和Java語
義模塊,讓你可以編寫自己的DRL文件。還將向你解釋如何編寫你自己的語義模塊,討論salience和working memory的概念。
作者
N. Alex Rupp is a freelance software architect and developer
from Minneapolis, and the current JSR94 Lead for the Drools project.