The Spark of Thinking

Focus on Eclipse Tools.

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  3 随笔 :: 27 文章 :: 4 评论 :: 0 Trackbacks
作者:保罗.布朗;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文件都被引用了。

image
图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所示,毫不奇怪的是,由于没有准备好适当的商务逻辑,测试在第二个申明处失败了。这个确信了模拟器/单元测试重点加强了他们应该有的问题。

image
图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所示,商务规则被调用了,逻辑评价正确,测试通过。祝贺—你已经创建了你的第一个基于规则的应用!

image
图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:YES
Name: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年的时间。
posted on 2006-07-21 16:58 The Spark of Thinking 阅读(684) 评论(0)  编辑  收藏 所属分类: Talk

只有注册用户登录后才能发表评论。


网站导航: