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