Blog信息 |
blog名称: 日志总数:1304 评论数量:2242 留言数量:5 访问次数:7620615 建立时间:2006年5月29日 |

| |
[Java Open Source]Drools-商务逻辑框架的选择 软件技术, 电脑与网络
lhwork 发表于 2006/6/12 13:33:50 |
Drools- 商务逻辑框架的选择
作者:保罗.布朗
译者:greenbright版权声明:任何获得Matrix授权的网站,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明作者:保罗.布朗;greenbright原文地址:http://www.onjava.com/pub/a/onjava/2005/08/03/drools.html中文地址:http://www.matrix.org.cn/resource/article/44/44046_Drools+Framework+Business.html关键词: Drools Framework Business大多数网络及企业级Jave应用可以分为三部分:和用户交互的前端,和后端系统(比如数据库)交互的服务层和这两部分之间的商务逻辑层。通常使用框架(像 Struts, Cocoon, Spring, Hibernate, JDO, 和实体Beans)可以实现前端和后端的功能,但对于商务逻辑层却没有一个标准的构建方法。像EJB和Spring只能在高端实现商务逻辑构建,但却不能组织代码。如果我们使用在配置性,可读性和重用性方面带我们极大利益的框架代替那些纷繁复杂的if...then语句,那不是很好吗?在其他领域我们已经验证了这种利益。这篇文章建议使用Drools规则引擎作为框架来解决问题。下面的代码是我们试图避免的问题的例子。说明一个典型Java应用中的一些商务逻辑。if ((user.isMemberOf(AdministratorGroup) && user.isMemberOf(teleworkerGroup)) || user.isSuperUser(){ // 更多对特殊案例的检查 if((expenseRequest.code().equals("B203") ||(expenseRequest.code().equals("A903") &&(totalExpenses<200) &&(bossSignOff> totalExpenses)) &&(deptBudget.notExceeded)) { //付款 } else if { //检查许多其他的条件 }} else { //更多商务逻辑}我们都曾经历过相类似或者更为复杂的商务逻辑。当试图在Java中采用标准方法来实施商务逻辑时,就有很多问题随之而来。· 如果商务人员提出需要在本已很难理解的代码中加入另外一个表单("C987")怎么办?一旦所有的最初开发人员都在开发中,你愿意成为一个维护人员吗?· 如何确认这些规则的正确与否?对技术人员来说(从来不关注商务人员),检查规则的正确性是非常困难的。那么,有什么方法论来测试商务逻辑那?许多应用有着类似的商务逻辑。如果其中一个规则发生了变化,我们能够确认所有系统中的相关部分都要改变吗?如果一个新应用使用了一些规则,但是也加入了一些新规则,我们需要彻底重写逻辑吗? · 商务逻辑嵌入在Java代码中,每次哪怕是很小的改变都需要再编译/再部署这些代码,这些商务逻辑容易配置吗?· 如果其他的(脚本)语言想平衡现有投资在商务规则逻辑中怎么办?J2EE/EJB和倒置控制的框架(像Spring, Pico和Avalon)让我们有能力在高端组织代码。他们很提供很好的重用性,配置性和安全性,但却不能替代上面例子中的“意大利面条式代码”。理想地,无论我们选择那个框架,不仅要和J2EE应用,而且要和通常Java编程(J2SE—标准版本)及广泛使用的表现和持续性框架相一致。这种框架让我们能够做到:· 商务用户很容易的读和验证商务逻辑。· 在应用中,商务规则是可重用的和可配置的。· 在重负荷下,框架是可扩展的和性能良好的。· Java编程人员对已存在的前端(Struts, Spring)和后端框架(对象关系映射)很容易使用这种框架。另一个问题是在不同的应用中,组织页面,数据库访问和商务逻辑的方法也是多种多样的。我们的框架能够处理这个问题,并能始终提升代码的重用性。理想地,我们的应用使用“适用所有方式的框架”。以这种方式使用框架,能够让我们许多的应用构建的更好,让我们可以仅关注那些对用户更有价值的部分。规则引擎的营救如何解决这个问题那? 一个解决方案是使用规则引擎。规则引擎是组织商务逻辑的框架。它让开发者集中精力在他们有把握的事情上,而不是在一些低级机制上作决定。通常,商务用户对那些能让他们理解是正确的事情感到更加舒服,相对于那些诸如用if...then 形式来表达的事情。你从商务专家那里听到的一些事情如下· “10A表单用于申请超过200欧元的花费.”· “我们仅对数量1万或超过1万的交易提供分成.”· “超过10m英镑的采购需要公司总监的批准.”通过关注于商务用户知道是正确的事情上,而不是怎样用Jave代码来表达它,上面的说明比以前我们的代码例子要清楚的多。尽管他们已经很清楚了,我们仍然需要一种机制,将这些规则应用到商务用户已知和作决定的事实中去。这种机制就是规则引擎。相关文章:在企业级Java应用中使用Drools企业级Java应用开发者在表现和持续层有很多好的框架可供选择。但对于处在中间层的商务逻辑有好的框架吗?你希望每次经理给你一个新的命令就不得不重编译那些复杂的if ... then 意大利面条代码吗?这篇文章中,保罗布朗推荐的Drools的规则引擎或许是完成这类任务的最好选择。Jave规则引擎JSR 94- javax.rules API制定了与规则引擎交互的通用标准,就像JDBC能够与各种类型的数据库交互。JSR-94并不是详细说明实际规则到底是怎么写的,而是最大限度的使用Java规则引擎来做这种详细说明。· Jess可能是最成熟的Java规则引擎,有很多好的工具(包括Eclipse)和文档。然而它是一个商业产品,并且它的规则是以序言样式符号写的,这对许多Java编程者都是一个挑战。· Jena是开源框架,始于HP.它有规则引擎,并且对那些关注于语义的网页特别支持。但它并不完全遵守JSR-94。· Drools 是完全遵守JSR-94的规则引擎。而且是Apache模式许可下的完全开源框架。它不仅用大家熟知的Java 和XML语法来表示规则,而且有很强的用户和开发者团体。本文章中的例子中,我们将使用Drools。Drools使用像Java的语法,并且有最开放的许可。用Drools开始开发Java应用想象一下这种情景:就在读了这篇文章几分钟后,你的老板让你给股票交易应用建原型。尽管商务用户还没有定义好商务逻辑,一个可以想到的好主意就是用规则引擎来实现。最终的系统将通过网络访问,并且需要和后端数据库和信息系统交流。为了开始使用这个框架,需要下载Drools框架(有依赖性)。如图1所示,在你喜欢的IDE中建一个新项目并确保所有的.jars文件都被引用了。500)this.width=500'>图1。运行Drools必需的类库由于潜在可能的巨大损失,如果我们的股票交易系统想要成功,在系统中循序渐进的使用一系列模拟器就显得很重要。这样的模拟器能够让你确信,即使规则改变了,由系统的作的决定也是正确的。我们将借用灵活工具箱的一些工具,并用JUnit框架作为模拟器。如下例所示,我们所写的第一段代码是Junit测试/模拟器。尽管我们不能测试所有输入系统的值的组合,由测试总比什么都不测好。在这个例子中,所有的文档和classes(包括单元测试)又在一个文件夹/包中;但事实上,由应该创建合适的包和文件夹结构。在代码中,我们用Log4j代替 System.out调用。清单1:import junit.framework.TestCase;/* * 应用中商务规则的JUnit测试 * 这也扮演商务规则的“模拟器“-让我们说明输入,检验输出;并在代码发*布前看看是否达到我的期望值。*/public class BusinessRuleTest extends TestCase {/***股票购买测试*/ public void testStockBuy() throws Exception{ //用模拟值创建股票 StockOffer testOffer = new StockOffer(); testOffer.setStockName("MEGACORP"); testOffer.setStockPrice(22); testOffer.setStockQuantity(1000); //运行规则 BusinessLayer.evaluateStockPurchase(testOffer); //达到我们的期望吗? assertTrue( testOffer.getRecommendPurchase()!=null); assertTrue("YES".equals( testOffer.getRecommendPurchase())); }}这是个基本Junit测试,我们知道这个简单系统应该买进所有价格低于100欧元的股票。很显然,没有数据类(StockOffer.java)和商务层类(BusinessLayer.java)这个测试用例是不能编译成功的。清单2:/***示例商务逻辑的正面*这个简单示例里,所有的商务逻辑都包含在一个类中。*但在现实中,按需要代理给其他的类。*/public class BusinessLayer { /** *评价购买这支股票是否是个好主意 *@参数 stockToBuy *@return 如果推荐购买股票返回真,否则返回假 */ public static void evaluateStockPurchase (StockOffer stockToBuy){ return false; }}StockOffer类如下所示:/*** 简单的JavaBean保存StockOffer值。* 一个’股票出价’就是别人卖出股票(公司股份)所给出的价格。*/public class StockOffer { //常量 public final static String YES="YES"; public final static String NO="NO"; //内部变量 private String stockName =null; private int stockPrice=0; private int stockQuantity=0; private String recommendPurchase = null;/** * @返回股票名称 */ public String getStockName() { return stockName; }/** * @参数 stockName 设置股票名称. */ public void setStockName(String stockName) { this.stockName = stockName; }/** * @return 返回股票价格. */ public int getStockPrice() { return stockPrice; }/** * @参数 stockPrice设置股票价格. */ public void setStockPrice(int stockPrice) { this.stockPrice = stockPrice; }/** * @return 返回股票数量. */ public int getStockQuantity() { return stockQuantity; }/** * @参数 stockQuantity 设置股票数量. */ public void setStockQuantity(int stockQuantity){ this.stockQuantity = stockQuantity; }/** * @return 返回建议购买. */ public String getRecommendPurchase() { return recommendPurchase; }}在我们熟悉的IDE的Junit中运行BusinessRuleTest。如果你不太熟悉Junit,可以从Junit网站获取更多信息。如图2所示,毫不奇怪的是,由于没有准备好适当的商务逻辑,测试在第二个申明处失败了。这个确信了模拟器/单元测试重点加强了他们应该有的问题。500)this.width=500'>图2。Junit测试结果用规则描述商务逻辑现在,我们需要描述一些商务逻辑,像“如果股票价格低于100欧元,应该购买。”这样我们将修改BusinessLayer.java为:清单3:import java.io.IOException;import org.drools.DroolsException;import org.drools.RuleBase;import org.drools.WorkingMemory;import org.drools.event.DebugWorkingMemoryEventListener;import org.drools.io.RuleBaseLoader;import org.xml.sax.SAXException;/***示例商务逻辑的正面*这个简单示例里,所有的商务逻辑都包含在一个类中。*但在现实中,按需要代理给其他的类。*@作者 缺省*/public class BusinessLayer {//包含规则文件的名字 private static final String BUSINESS_RULE_FILE= "BusinessRules.drl"; //内部处理的规则基础 private static RuleBase businessRules = null;/*** 如果还没有装载商务规则的话就装载它。*@抛出异常 -通常从这里恢复*/ private static void loadRules() throws Exception{ if (businessRules==null){ businessRules = RuleBaseLoader.loadFromUrl( BusinessLayer.class.getResource( BUSINESS_RULE_FILE ) ); } } /** *评价是否购买这支股票 *@参数 stockToBuy *@return 如果推荐购买股票返回真,否则返回假 *@抛出异常 */ public static void evaluateStockPurchase (StockOffer stockToBuy) throws Exception{ //确保商务规则被装载 loadRules();//一些程序进行的日志 System.out.println( "FIRE RULES" ); System.out.println( "----------" ); //了解以前运行的状态 WorkingMemory workingMemory = businessRules.newWorkingMemory();//小规则集可以添加调试侦听器 workingMemory.addEventListener( new DebugWorkingMemoryEventListener()); //让规则引擎了解实情 workingMemory.assertObject(stockToBuy); //让规则引擎工作 workingMemory.fireAllRules(); }}这个类有许多重要的方法:· loadRules()方法装载BusinessRules.drl文件中的规则。· 更新的evaluateStockPurchase()方法评价这些商务规则。这个方法中需要注意的是:· 同一个RuleSet可以被重复使用(内存中的商务规则是无状态的)。· 由于以我们的知识我们知道什么是正确的,每次评价我们使用一个新的WorkingMemory类。在内存中用assertObject()方法存放已知的实事(作为Java对象)。· Drools有一个事件侦听模式,能让我们“看到“事件模式内到底发生了什么事情。在这里我们用它打印出调试信息。Working memory类的fireAllRules()方法让规则被评价和更新(在这个例子中,stock offer)。再次运行例子之前,我们需要创建如下BusinessRules.drl文件:清单4:<?xml version="1.0"?><rule-set name="BusinessRulesSample" xmlns="http://drools.org/rules" xmlns:java="http://drools.org/semantics/java" xmlns:xs ="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation ="http://drools.org/rules rules.xsd http://drools.org/semantics/java java.xsd"> <!-- Import the Java Objects that we refer to in our rules --> <java:import> java.lang.Object </java:import> <java:import> java.lang.String </java:import> <java:import> net.firstpartners.rp.StockOffer </java:import> <!-- A Java (Utility) function we reference in our rules--> <java:functions> public void printStock( net.firstpartners.rp.StockOffer stock) { System.out.println("Name:" +stock.getStockName() +" Price: "+stock.getStockPrice() +" BUY:" +stock.getRecommendPurchase()); } </java:functions><rule-set> <!-- Ensure stock price is not too high--> <rule name="Stock Price Low Enough"> <!-- Params to pass to business rule --> <parameter identifier="stockOffer"> <class>StockOffer</class> </parameter> <!-- Conditions or 'Left Hand Side' (LHS) that must be met for business rule to fire --> <!-- note markup --> <java:condition> stockOffer.getRecommendPurchase() == null </java:condition> <java:condition> stockOffer.getStockPrice() < 100 </java:condition> <!-- What happens when the business rule is activated --> <java:consequence> stockOffer.setRecommendPurchase( StockOffer.YES); printStock(stockOffer); </java:consequence> </rule></rule-set>这个规则文件有几个有意思的部分:· 在XML-Schema定义被引入Java对象后,我们也把它引入到我们的规则中。这些对象来自于所需的Java类库。· 功能和标准的Java代码相结合。这样的话,我们就可以通过功能日志了解到底发生了什么。· 规则设置可以包括一个或多个规则。· 每条规则可以有参数(StockOffer类)。需要满足一个或多个条件,并且当条件满足时就执行相应的结果。修改,编译了代码后,再运行Junit测试模拟器。这次,如图3所示,商务规则被调用了,逻辑评价正确,测试通过。祝贺—你已经创建了你的第一个基于规则的应用!500)this.width=500'>图3。成功的Junit测试灵活的规则刚建好系统,你示范了上面的原型给商务用户,这时他们想起先前忘了给你提到几个规则了。新规则之一是当数量是负值时,不能够交易股票。你说“没问题,”,然后回到你的座位,借可靠的知识,你能迅速的改进系统。第一件要做的事情是更新你的模拟器,把下面的代码加到BusinessRuleTest.java:清单5:/***测试买股票确保系统不接受负值*/ public void testNegativeStockBuy() throws Exception{//用模拟值创建股票 StockOffer testOffer = new StockOffer(); testOffer.setStockName("MEGACORP"); testOffer.setStockPrice(-22); testOffer.setStockQuantity(1000);//运行规则 BusinessLayer .evaluateStockPurchase(testOffer);//是否达到我们的期望? assertTrue("NO".equals( testOffer.getRecommendPurchase()));}这是为商务用户描述的新规则的测试。如果测试这个Junit测试,如预期的新测试失败了。我们需要加这个新规则到.drl文件中,如下所示。清单6:<!-- Ensure that negative prices are not accepted--> <rule name="Stock Price Not Negative"> <!-- Parameters we can pass into the business rule --> <parameter identifier="stockOffer"> <class>StockOffer</class> </parameter> <!-- Conditions or 'Left Hand Side' (LHS) that must be met for rule to fire --> <java:condition> stockOffer.getStockPrice() < 0 </java:condition> <!-- What happens when the business rule is activated --> <java:consequence> stockOffer.setRecommendPurchase( StockOffer.NO); printStock(stockOffer); </java:consequence> </rule>这个规则的和前一个类似,期望<java:condition>不同(测试负值)和<java:consequence>设置建议购买为No。再次运行测试/模拟器,这次测试通过。这样的话,如果你习惯使用过程编程(象大多数Java 编程者),或许你会发愁挠头:一个文件包含两个独立的商务规则,而且我们也没有告诉规则引擎这两个规则哪个更重要。然而,股票价格(-22)也符合两个规则(它小于0也小于100)。不论如何,我们得到了正确的结果,即使交换规则的顺序。这是怎么工作的那?下面控制台输出的摘录帮助我们明白到底发生了什么。我们看到两个规则都被触发了([activationfired]行)并且Recommend Buy先被置为Yes然后被置为No。Drools是如何以正确的顺序来触发这些规则的那?如果你看Stock Price Low Enough规则,就会看到其中的一个条件recommendPurchase()是null.这就足以让Drools决定Stock Price Low Enough规则应该先于Stock Price Not Negative规则触发。这个过程就是冲突解决方案。清单7:FIRE RULES----------[ConditionTested: rule=Stock Price Not Negative; condition=[Condition: stockOffer.getStockPrice() < 0]; passed=true; tuple={[]}][ActivationCreated: rule=Stock Price Not Negative; tuple={[]}][ObjectAsserted: handle=[fid:2]; object=net.firstpartners.rp.StockOffer@16546ef][ActivationFired: rule=Stock Price Low Enough; tuple={[]}][ActivationFired: rule=Stock Price Not Negative; tuple={[]}]Name:MEGACORP Price: -22 BUY:YESName:MEGACORP Price: -22 BUY:NO如果你是个过程编程者,无论你认为这是多聪明,你仍然不能完全的信任它。这是为什么我们使用单元测试/模拟器:“辛苦的”Junit测试(使用通用Java 代码)保证规则引擎用我们关注的行来做决定。(不要花费上亿在一些无价值的股票上!)同时,规则引擎的强大和灵活性让我们能够迅速开发商务逻辑。稍后,我们将看到更多冲突解决方案的经典的方式。冲突解决方案在商务这边,人们真的印象深刻并别开始通过可能的选择来思考。他们看到XYZ公司股票的问题并且决定执行一条新规则:如果XYZ公司的股价低于10欧元,就仅仅买XYZ公司的股票。象上次,加测试到模拟器中,在规则文件中加入新的商务规则,如下所列。首先,在BusinessRuleTest.java中添加新方法。清单8:/***确保系统系统在XYZ公司股价便宜时就购买他们的股票*/public void testXYZStockBuy() throws Exception{//用模拟值创建股票 StockOffer testOfferLow = new StockOffer(); StockOffer testOfferHigh = new StockOffer(); testOfferLow.setStockName("XYZ"); testOfferLow.setStockPrice(9); testOfferLow.setStockQuantity(1000); testOfferHigh.setStockName("XYZ"); testOfferHigh.setStockPrice(11); testOfferHigh.setStockQuantity(1000);//运行规则 BusinessLayer.evaluateStockPurchase( testOfferLow); assertTrue("YES".equals( testOfferLow.getRecommendPurchase())); BusinessLayer.evaluateStockPurchase( testOfferHigh); assertTrue("NO".equals( testOfferHigh.getRecommendPurchase())); }然后,在BusinessRules.drl中加个新<规则>:清单10: <rule name="XYZCorp" salience="-1"> <!-- Parameters we pass to rule --> <parameter identifier="stockOffer"> <class>StockOffer</class> </parameter> <java:condition> stockOffer.getStockName().equals("XYZ") </java:condition> <java:condition> stockOffer.getRecommendPurchase() == null </java:condition> <java:condition> stockOffer.getStockPrice() > 10 </java:condition> <!-- What happens when the business rule is activated --> <java:consequence> stockOffer.setRecommendPurchase( StockOffer.NO); printStock(stockOffer); </java:consequence> </rule>注意到商务规则文件中,规则名字后,我们设置salience为-1(比如,到现在我们说明的所有规则中的最低优先级)。系统冲突(意味着Drools在触发那个规则的顺序上作决定)的大多数规则给出了将达到的规则的条件。缺省的决定方法如下:.· 显著性:如上列出的我们分配的值。 · 崭新性:我们使用规则的次数。 · 复杂性:第一次触发的更复杂的特定规则。 · 装载顺序:那个规则被装载的顺序。如果我们不说明这个例子中规则的显著性,将会发生的是: · XYZ公司规则(“如果股票价格超过10欧元买XYZ”)将被首先触发(推荐买标记的状态被置为No)。 · 然后是更多的普通规则被触发(“买所有低于100的股票”),推荐买标记的状态被置为yes。这将得到我们不期望的结果。然而,我们的例子设置了显著性因素,用例及商务规则就会如我们期望的运行了。大多数情况下,书写清楚的规则且设置显著性将为Drools提供足够的信息来选择触发规则的顺序。有时,我们想整个的改变解决规则冲突的方式。以下是一个如何改变的例子,其中我告诉规则引擎首先触发最简单的规则。要注意的是,改变冲突解决方案是要小心,因为它能根本的改变规则引擎的规则—用清楚的写得恰当的规则能预先解决许多问题。清单11://生成冲突解决者的列表 ConflictResolver[] conflictResolvers = new ConflictResolver[] { SalienceConflictResolver.getInstance(), RecencyConflictResolver.getInstance(), SimplicityConflictResolver.getInstance(), LoadOrderConflictResolver.getInstance() };//包装成合成解决者 CompositeConflictResolver resolver = new CompositeConflictResolver( conflictResolvers); //当装载规则时,说明这个解决者 businessRules = RuleBaseLoader.loadFromUrl( BusinessLayer.class.getResource( BUSINESS_RULE_FILE),resolver);
对于我们的简单的应用,由Junit测试驱动,我们不需要改变Drools解决规则冲突的方式。了解冲突解决方案是如何工作的对我们是非常有帮助的,尤其是当应用需要满足更复杂根严格的需求试。结论这篇文章论证了许多编程者遇到过的问题:如何定制复杂的商务逻辑。我们示范了一个用Drools为解决方案的简单应用并介绍了基于规则编程的概念,包括在运行时这些规则是如何被处理的。稍后,接下来的文章将以这些为基础来展示在企业级Java应用中是如何使用它的。资源·下载例子所有源代码:源代码·Matrix-Java开发者社区:http://www.matrix.org.cn·onjava.com:onjava.com·Drools项目主页·Drools规则信息·“Drools和规则引擎介绍”由Drools项目领导 ·Drools规则计划文件·JSR-94, Java规则引擎,概略 ·Jess Jave规则引擎·Jena语义和规则引擎·JSR-94主页·Jess在Action主页·“商务规则思想”(基于Jess) ·“规则系统的一般介绍” ·“Rete算法的Jess执行”保尔 布朗已经通过FirstPartners.net网站为企业级Java咨询了约7年的时间。 |
|
|