为了在公司的应用框架中集成规则引擎,我这几天一直在苦苦研读N多的英文文档,但始终没有掌握其基本的概念。直到我读了Alex Rupp发表在TheServerSide的文章,才开始整理出一些头绪,太精彩了!现将它译成中文推荐给大家。原文参见
这里。
内容提要
在本文的第一部分,我将讨论规则引擎如何帮助你从软件的应用逻辑中分离出商业规则逻辑,以实现商业应用的灵活性。另外,我还将介绍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.