內(nèi)容提要
在本文的第一部分,我將討論規(guī)則引擎如何幫助你從軟件的應(yīng)用邏輯中分離出商業(yè)規(guī)則邏輯,以實(shí)現(xiàn)商業(yè)應(yīng)用的靈活性。在第二部分,我還將介紹
JSR
-
94
規(guī)則引擎
API
,及其實(shí)現(xiàn)原理。在第三部分
,
介紹其開(kāi)源實(shí)現(xiàn)
Drools
項(xiàng)目,它是這一新技術(shù)的先驅(qū),并詳解一個(gè)規(guī)則引擎例子。
一、規(guī)則引擎如何幫助你從軟件的應(yīng)用邏輯中分離出商業(yè)規(guī)則邏輯,以實(shí)現(xiàn)商業(yè)應(yīng)用的靈活性。
定義
:
規(guī)則引擎:由基于規(guī)則的專家系統(tǒng)中的推理引擎發(fā)展而來(lái)
,
是一種嵌入在應(yīng)用程序中的組件,實(shí)現(xiàn)了將業(yè)務(wù)決策從應(yīng)用程序代碼中分離出來(lái),并使用預(yù)定義的語(yǔ)義模塊編寫(xiě)業(yè)務(wù)決策。接受數(shù)據(jù)輸入,解釋業(yè)務(wù)規(guī)則,并根據(jù)規(guī)則做出業(yè)務(wù)決策。
為什么使用規(guī)則引擎
“任何事物都會(huì)改變,唯一不變的是變化”…,這些陳詞濫調(diào)已經(jīng)在我們耳邊磨出糨子來(lái)了. 無(wú)論是快速軟件開(kāi)發(fā),極限編程,還是敏捷軟件開(kāi)發(fā)等,它們 無(wú)一例外地強(qiáng)調(diào)靈活和變化的重要性。
雖然IT團(tuán)隊(duì)反應(yīng)迅速,但他們通常帶來(lái)"電話效應(yīng)"――IT給商業(yè)計(jì)劃的執(zhí)行帶來(lái)的阻力和它帶來(lái)的利益一樣多。不幸的是,在開(kāi)發(fā)團(tuán)隊(duì)完全理解商業(yè)決策規(guī)則并實(shí)現(xiàn)之前,規(guī)則已經(jīng)改變了。在軟件進(jìn)入市場(chǎng)前,它已經(jīng)過(guò)時(shí)了,需要進(jìn)行重構(gòu)以滿足新的業(yè)務(wù)需求。如果你是一個(gè)開(kāi)發(fā)人員,你會(huì)知道我在說(shuō)什么。再也沒(méi)有比 在需求變動(dòng)的情況下構(gòu)造軟件讓開(kāi)發(fā)人員更沮喪的事情了。作為軟件開(kāi)發(fā)人員,你必須比業(yè)務(wù)人員更了解業(yè)務(wù),有時(shí)還要了解更多。
??? 試想一下在完成產(chǎn)品開(kāi)發(fā)時(shí),需求(市場(chǎng)環(huán)境或人為因素,有些需求無(wú)法在項(xiàng)目初始就能預(yù)見(jiàn)的)和規(guī)劃產(chǎn)品開(kāi)發(fā)時(shí)相比,已經(jīng)發(fā)生了根本變化。現(xiàn)在你必須要遵守新的規(guī)則,你已經(jīng)喪失了你的邊際優(yōu)勢(shì),而且設(shè) 計(jì)軟件的五人中的三人已經(jīng)離開(kāi)了公司。你必須給接手的新人重新講解復(fù)雜的業(yè)務(wù)。如果事情不順利,你可能發(fā)現(xiàn)自己要對(duì)付一個(gè)缺少文檔,并且你完全不了解的遺留應(yīng)用。
你的戰(zhàn)略在哪出現(xiàn)了問(wèn)題?你在哪里應(yīng)該可以做到更好?當(dāng)前在Java社區(qū),一個(gè)引人注目的新技術(shù)是,分離商業(yè)決策者的商業(yè)決策邏輯和應(yīng)用開(kāi)發(fā)者的技術(shù)決策,并把這些商業(yè)決策放在中心數(shù)據(jù)庫(kù),讓它們能在運(yùn)行時(shí)(即商務(wù)時(shí)間)可以動(dòng)態(tài)地管理和修改。這是一個(gè)你值得考慮的策略。
為什么你的開(kāi)發(fā)團(tuán)隊(duì)不得在代碼中包含復(fù)雜微妙的商業(yè)決策邏輯呢?你怎樣才能向他們解釋決策推理的微妙之處呢?你這樣做是否謹(jǐn)慎呢?可能不是。你一方面要應(yīng)付市場(chǎng),一方面要應(yīng)付軟件代碼,這實(shí)在太困難了。如果能將這些商業(yè)決策規(guī)則集中地放在一個(gè)地方,以一種你可以理解的格式定義,讓你可以直接管理,而不是散落在代碼的各個(gè)角落,那該有多好。如果你能把商業(yè)決策規(guī)則獨(dú)立于你的軟件代碼,讓開(kāi)發(fā)團(tuán)隊(duì)作出技術(shù)決策,你將會(huì)獲得更多好處。你的項(xiàng)目開(kāi)發(fā)周期會(huì)更短,軟件對(duì)于變動(dòng)的需求更靈活。
分離業(yè)務(wù)和技術(shù)的關(guān)注點(diǎn)
這是一個(gè)非常簡(jiǎn)單的例子,從業(yè)務(wù)人員的角度,說(shuō)明如何分離商務(wù)和技術(shù)的關(guān)注點(diǎn)。
CRM
系統(tǒng)新裝
ADSL
。系統(tǒng)的一部分用于分析并受理可供客戶購(gòu)買(mǎi)的銷售品
,
并在需要時(shí)向你提出相應(yīng)的提示。這個(gè)系統(tǒng)的工作是,識(shí)別出產(chǎn)品,并標(biāo)記出來(lái)以便進(jìn)一步的檢查。
前置條件
:
CRM
開(kāi)發(fā)團(tuán)隊(duì)擁有一大堆數(shù)據(jù),并開(kāi)發(fā)了一系列你可以在規(guī)則中引用的簡(jiǎn)單數(shù)據(jù)對(duì)象。現(xiàn)在,為簡(jiǎn)單起見(jiàn),假設(shè)你是一名受過(guò)良好教育的,了解技術(shù)的管理人,你了解
XML
的基本知識(shí),可以讓你編寫(xiě)和修改簡(jiǎn)單的
XML
規(guī)則文件。
你的第一個(gè)規(guī)則是,給在眾多受理的產(chǎn)品
(
有上百個(gè)產(chǎn)品
,
且只有一個(gè)入口和一個(gè)出品
)
中剔除非
ADSL
的產(chǎn)品(這有點(diǎn)過(guò)分簡(jiǎn)化,但這里只作為一個(gè)例子)
,
并給
ADSL
承載電話進(jìn)行合法性校驗(yàn)。該電話先前不能有承載的
ADSL.
。對(duì)于這個(gè)簡(jiǎn)單的例子,你的規(guī)則文件看起來(lái)如下(我們將會(huì)過(guò)頭來(lái)討論這個(gè)文件的結(jié)構(gòu)):






半個(gè)月后,你接到電信公司的電話,對(duì)于
ADSL
選擇寬帶上網(wǎng)卡的用戶承電話承載的
ADSL
可不受限制。除此之外,你的客戶還要求
XXX
本地網(wǎng)內(nèi)的某類客戶承載電話承載的
ADSL
不能超過(guò)
5
個(gè)。
你啟動(dòng)規(guī)則編輯器,并修改規(guī)則以匹配新的評(píng)估條件。完成規(guī)則文件修改后,看起來(lái)如下:






















你無(wú)需為此向開(kāi)發(fā)團(tuán)隊(duì)作任何解釋。你無(wú)需等待他們開(kāi)發(fā)或測(cè)試程序。如果你的規(guī)則引擎的語(yǔ)義足夠強(qiáng)大,讓你描述工作數(shù)據(jù),你可以隨時(shí)按需修改商業(yè)規(guī)則。
現(xiàn)在,我希望你已經(jīng)清楚以下的原則:在這個(gè)例子中,
ADSL
承載電話是否合法是一個(gè)業(yè)務(wù)決策,而不是技術(shù)決策。決定將哪個(gè)承載電話是否通過(guò)是業(yè)務(wù)人員的邏輯
。業(yè)務(wù)人員作出這些決策,并可以按需定制應(yīng)用。這些規(guī)則因此變成了一種控制界面,一種新的商業(yè)系統(tǒng)用戶界面。
二、
Java
規(guī)則引擎
API JSR-94,
及其實(shí)現(xiàn)原理。
2003
年
11
月,
為了使規(guī)則引擎技術(shù)標(biāo)準(zhǔn)化,
Java
社區(qū)制定了
Java
規(guī)則引擎
API
(
JSR94
)規(guī)范。它為
Java
平臺(tái)訪問(wèn)規(guī)則引擎定義了一些簡(jiǎn)單的
API
。
這個(gè)新的
API
讓開(kāi)發(fā)人員在運(yùn)行時(shí)訪問(wèn)和執(zhí)行規(guī)則有了統(tǒng)一的標(biāo)準(zhǔn)方式。隨著新規(guī)范產(chǎn)品實(shí)現(xiàn)的成熟和推向市場(chǎng),開(kāi)發(fā)
團(tuán)隊(duì)將可以從應(yīng)用代碼中抽取出商業(yè)決策邏輯。
Java
規(guī)則引擎
API
在
javax.rules
包中定義,是訪問(wèn)規(guī)則引擎的標(biāo)準(zhǔn)企業(yè)級(jí)
API
。
Java
規(guī)則引擎
API
允許客戶程序使用統(tǒng)一的方式和不
同廠商的規(guī)則引擎產(chǎn)品交互,就如同使用
JDBC
編寫(xiě)?yīng)毩⒂趶S商訪問(wèn)不同的數(shù)據(jù)庫(kù)產(chǎn)品一樣。
Java
規(guī)則引擎
API
包括創(chuàng)建和管理規(guī)則集合的機(jī)制,在工作區(qū)
中添加,刪除和修改對(duì)象的機(jī)制,以及初始化,重置和執(zhí)行規(guī)則引擎的機(jī)制。
Java
規(guī)則引擎
API
主要由兩大類
API
組成
:
規(guī)則管理
API(The Rules Administrator API)
和運(yùn)行時(shí)客戶
API(The Runtime Client API)
。
Java
規(guī)則引擎是一種嵌入在
Java
程序中的組件,它的任務(wù)是把當(dāng)前提交給引擎的
Java
數(shù)據(jù)對(duì)象與加載在引擎中的業(yè)務(wù)規(guī)則進(jìn)行測(cè)試和比對(duì),激活那些符合當(dāng)前數(shù)據(jù)狀態(tài)下的業(yè)務(wù)規(guī)則,根據(jù)業(yè)務(wù)規(guī)則中聲明的執(zhí)行邏輯,觸發(fā)應(yīng)用程序中對(duì)應(yīng)的操作。
Java
規(guī)則引擎的工作機(jī)制與上述規(guī)則引擎機(jī)制十分類似,只不過(guò)對(duì)上述概念進(jìn)行了重新包裝組合。Java規(guī)則引擎對(duì)提交給引擎的Java數(shù)據(jù)對(duì)象進(jìn)行檢 索,根據(jù)這些對(duì)象的當(dāng)前屬性值和它們之間的關(guān)系,從加載到引擎的規(guī)則集中發(fā)現(xiàn)符合條件的規(guī)則,創(chuàng)建這些規(guī)則的執(zhí)行實(shí)例。這些實(shí)例將在引擎接到執(zhí)行指令時(shí)、依照某種優(yōu)先序依次執(zhí)行。一般來(lái)講,Java規(guī)則引擎內(nèi)部由下面幾個(gè)部分構(gòu)成:工作內(nèi)存(Working Memory)即工作區(qū),用于存放被引擎引用的數(shù)據(jù)對(duì)象集合;規(guī)則執(zhí)行隊(duì)列,用于存放被激活的規(guī)則執(zhí)行實(shí)例;靜態(tài)規(guī)則區(qū),用于存放所有被加載的業(yè)務(wù)規(guī)則, 這些規(guī)則將按照某種數(shù)據(jù)結(jié)構(gòu)組織,當(dāng)工作區(qū)中的數(shù)據(jù)發(fā)生改變后,引擎需要迅速根據(jù)工作區(qū)中的對(duì)象現(xiàn)狀,調(diào)整規(guī)則執(zhí)行隊(duì)列中的規(guī)則執(zhí)行實(shí)例。Java規(guī)則引 擎的結(jié)構(gòu)示意圖如下圖所示。

|
當(dāng)引擎執(zhí)行時(shí),會(huì)根據(jù)規(guī)則執(zhí)行隊(duì)列中的優(yōu)先順序逐條執(zhí)行規(guī)則執(zhí)行實(shí)例,由于規(guī)則的執(zhí)行部分可能會(huì)改變工作區(qū)的數(shù)據(jù)對(duì)象,從而會(huì)使隊(duì)列中的某些規(guī)則執(zhí)行實(shí)例因?yàn)闂l件改變而失效,必須從隊(duì)列中撤銷,也可能會(huì)激活原來(lái)不滿足條件的規(guī)則,生成新的規(guī)則執(zhí)行實(shí)例進(jìn)入隊(duì)列。于是就產(chǎn)生了一種“動(dòng)態(tài)”的規(guī)則執(zhí)行鏈,形 成規(guī)則的推理機(jī)制。這種規(guī)則的“鏈?zhǔn)?span lang="EN-US">”反應(yīng)完全是由工作區(qū)中的數(shù)據(jù)驅(qū)動(dòng)的。
任何一個(gè)規(guī)則引擎都需要很好地解決規(guī)則的推理機(jī)制和規(guī) 則條件匹配的效率問(wèn)題。規(guī)則條件匹配的效率決定了引擎的性能,引擎需要迅速測(cè)試工作區(qū)中的數(shù)據(jù)對(duì)象,從加載的規(guī)則集中發(fā)現(xiàn)符合條件的規(guī)則,生成規(guī)則執(zhí)行實(shí)例。1982年美國(guó)卡耐基?梅隆大學(xué)的Charles L. Forgy發(fā)明了一種叫Rete算法,很好地解決了這方面的問(wèn)題。目前世界頂尖的商用業(yè)務(wù)規(guī)則引擎產(chǎn)品基本上都使用Rete算法。
三、開(kāi)源實(shí)現(xiàn)
Drools
項(xiàng)目
現(xiàn)在,我要介紹
Drools
項(xiàng)目,
Charles Forgy Rete
算法的一個(gè)增強(qiáng)的
Java
語(yǔ)言實(shí)現(xiàn)。
Drools
是一個(gè)
Bob McWhirter
開(kāi)發(fā)的開(kāi)源項(xiàng)目,放在
The Codehaus
上。在我寫(xiě)這篇文章時(shí),
Drools
發(fā)表了
2.0-beata-14
版。在
CVS
中,已完整地實(shí)現(xiàn)了
JSR94 Rule Engine API
并提供了單元測(cè)試代碼。
Rete
算法是
Charles Forgy
在
1979
年發(fā)明的,是目前用于生產(chǎn)系統(tǒng)的效率最高的算法(除了私有的
Rete II
)。
Rete
是唯一的,效率與執(zhí)行規(guī)則數(shù)目無(wú)關(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
開(kāi)源軟件中并沒(méi)有得到廣泛應(yīng)用(討論
Rete
算法的文檔參見(jiàn)
http: //herzberg.ca.sandia.gov/jess/docs/61/rete.html
。)。
除了應(yīng)用了
Rete
核心算法,開(kāi)源軟件
License
和
100
%的
Java
實(shí)現(xiàn)之外,
Drools
還提供了很多有用的特性。其中包括實(shí)現(xiàn)了
JSR94 API
和創(chuàng)新的規(guī)則語(yǔ)義系統(tǒng),這個(gè)語(yǔ)義系統(tǒng)可用來(lái)編寫(xiě)描述規(guī)則的語(yǔ)言。目前,
Drools
提供了三種語(yǔ)義模塊
――Python
模塊,
Java
模塊和
Groovy
模塊。本文余下部分集中討論
JSR94 API
,我將在第二篇文章中討論語(yǔ)義系統(tǒng)。
作為使用
javax.rules API
的開(kāi)發(fā)人員,你的目標(biāo)是構(gòu)造一個(gè)
RuleExecutionSet
對(duì)象,并在運(yùn)行時(shí)通過(guò)它獲得一個(gè)
RuleSession
對(duì)象。為了簡(jiǎn)化這個(gè)過(guò)程,
我編寫(xiě)了一個(gè)規(guī)則引擎
API
的
fa?ade
,可以用來(lái)解釋代表
Drools
的
DRL
文件的
InputStream
,并構(gòu)造一個(gè)
RuleExecutionSet
對(duì)象。
在上面提到了
Drools
的三種語(yǔ)義模塊,我接下來(lái)使用它們重新編寫(xiě)上面的例子
XML
規(guī)則文件。這個(gè)例子中我選擇
Java
模塊。使用
Java
模塊重新編寫(xiě)的規(guī)則文件如下:


























































現(xiàn)在的規(guī)則文件并沒(méi)有上面的簡(jiǎn)潔明了。別擔(dān)心,我們將在下一篇文章討論語(yǔ)義模塊。現(xiàn)在,請(qǐng)注意觀察XML文件的結(jié)構(gòu)。其中一個(gè)rule-set元素包含了一個(gè)或多個(gè)rule元素,rule元素又包含了parameter,condition和consequence元素。Condition和 consequence元素包含的內(nèi)容和Java很象。注意,在這些元素中,有些事你可以做,有些事你不能做。目前,Drools使用 BeanShell2.0b1作為它的Java解釋器。我在這里并不想詳細(xì)的討論DRL文件和Java語(yǔ)義模塊的語(yǔ)法。我們的目標(biāo)是解釋如何使用 Drools的JSR94 API。
在Drools項(xiàng)目CVS的drools-jsr94模塊中,單元測(cè)試代碼包含了一個(gè)ExampleRuleEngineFacade對(duì)象,它基于 Brian Topping的Dentaku項(xiàng)目。這個(gè)facade對(duì)象通過(guò)javax.rules API,創(chuàng)建了供RuleExecutionSet和RuleSession使用的一系列對(duì)象。它并沒(méi)有完全包括了Drools引擎API的所有特性和細(xì)微差別,但可以作為新手使用API的一個(gè)簡(jiǎn)單例子。
下面的代碼片斷顯示如何在程序中調(diào)用drools規(guī)則引擎并執(zhí)行。
















?public interface WorkingEnvironmentCallback{
?void initEnvironment(WorkingMemory workingMemory) throws FactException;
}?
public?class?RulesEngine{
????private?static?Logger????logger????=Logger.getLogger(RulesEngine.class);
????private?RuleBase????????rules;
????private?String????????????rulesFile;
????private?boolean????????????debug????=false;
????/**?*//**
?????*?構(gòu)造方法,裝載規(guī)則文件
?????*?
?????*?@param?rulesFile
?????*????????????絕對(duì)規(guī)則文件路徑
?????*?@throws?RulesEngineException
?????*/
????public?RulesEngine(final?String?rulesFile)?throws?RulesEngineException
{
????????super();
????????this.rulesFile=rulesFile;
????????try
{
????????????????rules=RuleBaseLoader.loadFromInputStream(new?BufferedInputStream(new?FileInputStream(
????????????????????????new?File(rulesFile))));
????????????}
????????}catch(Exception?e)
{
????????????e.printStackTrace();
????????????throw?new?RulesEngineException("Could?not?load?rules?file:?"?+?rulesFile,e);
????????}
????}
????public?RulesEngine(final?URL?rulesFile)?throws?RulesEngineException
{
????????super();
????????this.rulesFile=rulesFile.getPath();
????????try
{
????????????????rules=RuleBaseLoader.loadFromUrl(rulesFile);
????????????????
????????????}
????????}catch(Exception?e)
{
????????????e.printStackTrace();
????????????throw?new?RulesEngineException("Could?not?load?rules?file:?"?+?rulesFile,e);
????????}
????}
????/**?*//**
?????*?構(gòu)造方法,裝載規(guī)則文件/debug模式
?????*?
?????*?@param?rulesFile
?????*?@param?debug
?????*?@throws?RulesEngineException
?????*/
????public?RulesEngine(URL?rulesFile,boolean?debug)?throws?RulesEngineException
{
????????this(rulesFile);
????????this.debug=debug;
????}
????/**?*//**
?????*?構(gòu)造方法,裝載規(guī)則文件/debug模式
?????*?
?????*?@param?rulesFile
?????*?@param?debug
?????*?@throws?RulesEngineException
?????*/
????public?RulesEngine(String?rulesFile,boolean?debug)?throws?RulesEngineException
{
????????this(rulesFile);
????????this.debug=debug;
????}
????/**?*//**
?????*?執(zhí)行規(guī)則
?????*?
?????*?@param?callback
?????*?@return
?????*?@throws?RulesEngineException
?????*/
????public?List?executeRules(WorkingEnvironmentCallback?callback)?throws?RulesEngineException
{
????????try
{
????????????WorkingMemory?workingMemory=rules.newWorkingMemory();
????????????if(debug)
{
????????????????workingMemory.addEventListener(new?DebugWorkingMemoryEventListener());
????????????}
????????????callback.initEnvironment(workingMemory);
????????????workingMemory.fireAllRules();
????????????return?workingMemory.getObjects();
????????}catch(FactException?fe)
{
????????????logFactException(fe);
????????????throw?new?RulesEngineException("Exception?occurred?while?attempting?to?execute?"?+?"rules?file:?"
????????????????????+?rulesFile,fe);
????????}
????}
????private?void?logFactException(FactException?fe)
{
????????if(fe?instanceof?ConditionException)
{
????????????ConditionException?ce=(ConditionException)fe;
????????????logger.error("Rule?where?condition?exception?occurred:?"?+?ce.getRule().getName());
????????}else?if(fe?instanceof?ConsequenceException)
{
????????????ConsequenceException?ce=(ConsequenceException)fe;
????????????logger.error("Information?about?the?consequence?failure:?"?+?ce.getInfo());
????????????logger.error("Rule?where?consequence?exception?occurred:?"?+?ce.getRule().getName());
????????}
????}
}
結(jié)束語(yǔ)
規(guī)則引擎技術(shù)為管理多變的業(yè)務(wù)邏輯提供了一種解決方案。規(guī)則引擎既可以管理應(yīng)用層的業(yè)務(wù)邏輯又可以使表示層的頁(yè)面流程可訂制。這就給軟件架構(gòu)師設(shè)計(jì)大型信息系統(tǒng)提供了一項(xiàng)新的選擇。而 Java 規(guī)則引擎在 Java 社區(qū)制定標(biāo)準(zhǔn)規(guī)范以后必將獲得更大發(fā)展。