内容提要
在本文的第一部分,我将讨论规则引擎如何帮助你从软件的应用逻辑中分离出商业规则逻辑,以实现商业应用的灵活性。在第二部分,我还将介绍 JSR - 94 规则引擎 API ,及其实现原理。在第三部分 , 介绍其开源实现 Drools 项目,它是这一新技术的先驱,并详解一个规则引擎例子。

 

一、规则引擎如何帮助你从软件的应用逻辑中分离出商业规则逻辑,以实现商业应用的灵活性。

定义 :

规则引擎:由基于规则的专家系统中的推理引擎发展而来 , 是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据规则做出业务决策。

为什么使用规则引擎
“任何事物都会改变,唯一不变的是变化”…,这些陈词滥调已经在我们耳边磨出糨子来了. 无论是快速软件开发,极限编程,还是敏捷软件开发等,它们 无一例外地强调灵活和变化的重要性。

虽然IT团队反应迅速,但他们通常带来"电话效应"――IT给 商业计划的执行带来的阻力和它带来的利益一样多。不幸的是,在开发团队完全理解商业决策规则并实现之前,规则已经改变了。在软件进入市场前,它已经过时 了,需要进行重构以满足新的业务需求。如果你是一个开发人员,你会知道我在说什么。再也没有比 在需求变动的情况下构造软件让开发人员更沮丧的事情了。作为软件开发人员,你必须比业务人员更了解业务,有时还要了解更多。
    试想一下在完成产品开发时,需求(市场环境或人为因素,有些需求无法在项目初始就能预见的)和规划产品开发时相比,已经发生了根本变化。现在你必须要遵守新的规则,你已经丧失了你的边际优势,而且设 计软件的五人中的三人已经离开了公司。你必须给接手的新人重新讲解复杂的业务。如果事情不顺利,你可能发现自己要对付一个缺少文档,并且你完全不了解的遗留应用。
你的战略在哪出现了问题?你在哪里应该可以做到更好?当前在Java社区,一个引人注目的新技术是,分离商业决策者的商业决策逻辑和应用开发者的技术决策,并把这些商业决策放在中心数据库,让它们能在运行时(即商务时间)可以动态地管理和修改。这是一个你值得考虑的策略。
为 什么你的开发团队不得在代码中包含复杂微妙的商业决策逻辑呢?你怎样才能向他们解释决策推理的微妙之处呢?你这样做是否谨慎呢?可能不是。你一方面要应付 市场,一方面要应付软件代码,这实在太困难了。如果能将这些商业决策规则集中地放在一个地方,以一种你可以理解的格式定义,让你可以直接管理,而不是散落 在代码的各个角落,那该有多好。如果你能把商业决策规则独立于你的软件代码,让开发团队作出技术决策,你将会获得更多好处。你的项目开发周期会更短,软件 对于变动的需求更灵活。

 

分离业务和技术的关注点

这是一个非常简单的例子,从业务人员的角度,说明如何分离商务和技术的关注点。 CRM 系统新装 ADSL 。系统的一部分用于分析并受理可供客户购买的销售品 , 并在需要时向你提出相应的提示。这个系统的工作是,识别出产品,并标记出来以便进一步的检查。
前置条件 :

CRM 开发团队拥有一大堆数据,并开发了一系列你可以在规则中引用的简单数据对象。现在,为简单起见,假设你是一名受过良好教育的,了解技术的管理人,你了解 XML 的基本知识,可以让你编写和修改简单的 XML 规则文件。
你的第一个规则是,给在众多受理的产品 ( 有上百个产品 , 且只有一个入口和一个出品 ) 中剔除非 ADSL 的产品(这有点过分简化,但这里只作为一个例子) , 并给 ADSL 承载电话进行合法性校验。该电话先前不能有承载的 ADSL. 。对于这个简单的例子,你的规则文件看起来如下(我们将会过头来讨论这个文件的结构):



< 
 rule  
 name 
 ="Prd_1-0-A1-1307 -01 " 
 > 
  
 
 < 
 java:condition 
 > 
 map.get( “ prdId ” ).equals("1307") 
 </ 
 java:condition 
 > 
  
 
 < 
 java:condition 
 > 
 map.get( “ count ” ) 
 < 
 1 
 </java:condition 
 > 
  
 
 < 
 java:consequence 
 > 
 bean.setPass(true); 
 </ 
 java:consequence 
 > 
  
 
 </ 
 rule 
 >




半个月后,你接到电信公司的电话,对于 ADSL 选择宽带上网卡的用户承电话承载的 ADSL 可不受限制。除此之外,你的客户还要求 XXX 本地网内的某类客户承载电话承载的 ADSL 不能超过 5 个。
你启动规则编辑器,并修改规则以匹配新的评估条件。完成规则文件修改后,看起来如下:



<  rule  
 name 
 ="Prd_1-0-A1-1307-01" 
 > 
  

  < 
 java:condition 
 > 
 map.get( “ prdId ” ).equals("1307") 
 </ 
 java:condition 
 > 
  

  < 
 java:condition 
 > 
 !map.get( “ 计费方式 ” ).equals( “ 宽带上网卡 ” ) 
 </ 
 java:condition 
 > 
  

  < 
 java:condition 
 > 
 map.get( “ count ” ) 
 < 
 1 
 </java:condition 
 > 
  
  < 
 java:consequence 
 > 
 bean.setPass(true); 
 </ 
 java:consequence 
 > 
  

  </ 
 rule 
 > 
    

  < 
 rule  
 name 
 ="Prd_1-0-A1-1307-02" 
 > 
  

  < 
 java:condition 
 > 
 map.get( “ prdId ” ).equals("1307") 
 </ 
 java:condition 
 > 
  

  < 
 java:condition 
 > 
 map.get( “ 计费方式 ” ).equals( “ 宽带上网卡 ” ) 
 </ 
 java:condition 
 > 
  

  < 
 java:condition 
 > 
 (map.get( “ latnId ” ).equals( “ 552 ” )& 
 &map 
 .get( “ count ” )<5) || !map.get( “ latnId ” ).equals( “ 552 ” ) 
 </ 
 java:condition 
 > 
  
  < 
 java:consequence 
 > 
 bean.setPass(true); 
 </ 
 java:consequence 
 > 
  

  </ 
 rule 
 >



你无需为此向开发团队作任何解释。你无需等待他们开发或测试程序。如果你的规则引擎的语义足够强大,让你描述工作数据,你可以随时按需修改商业规则。
现在,我希望你已经清楚以下的原则:在这个例子中, ADSL 承载电话是否合法是一个业务决策,而不是技术决策。决定将哪个承载电话是否通过是业务人员的逻辑 。业务人员作出这些决策,并可以按需定制应用。这些规则因此变成了一种控制界面,一种新的商业系统用户界面。

 

二、 Java 规则引擎 API JSR-94, 及其实现原理。
2003 年 11 月, 为了使规则引擎技术标准化, Java 社区制定了 Java 规则引擎 API ( JSR94 )规范。它为 Java 平台访问规则引擎定义了一些简单的 API 。 这个新的 API 让开发人员在运行时访问和执行规则有了统一的标准方式。随着新规范产品实现的成熟和推向市场,开发 团队将可以从应用代码中抽取出商业决策逻辑。

    Java 规则引擎 API 在 javax.rules 包中定义,是访问规则引擎的标准企业级 API 。 Java 规则引擎 API 允许客户程序使用统一的方式和不 同厂商的规则引擎产品交互,就如同使用 JDBC 编写独立于厂商访问不同的数据库产品一样。 Java 规则引擎 API 包括创建和管理规则集合的机制,在工作区 中添加,删除和修改对象的机制,以及初始化,重置和执行规则引擎的机制。 Java 规则引擎 API 主要由两大类 API 组成 : 规则管理 API(The Rules Administrator API) 和运行时客户 API(The Runtime Client API) 。

 

Java 规则引擎是一种嵌入在 Java 程序中的组件,它的任务是把当前提交给引擎的 Java 数据对象与加载在引擎中的业务规则进行测试和比对,激活那些符合当前数据状态下的业务规则,根据业务规则中声明的执行逻辑,触发应用程序中对应的操作。

 

Java 规则引擎的工作机制与上述规则引擎机制十分类似,只不过对上述概念进行了重新包装组合。Java规则引擎对提交给引擎的Java数据对象进行检 索,根据这些对象的当前属性值和它们之间的关系,从加载到引擎的规则集中发现符合条件的规则,创建这些规则的执行实例。这些实例将在引擎接到执行指令时、依照某种优先序依次执行。一般来讲,Java规则引擎内部由下面几个部分构成:工作内存(Working Memory)即工作区,用于存放被引擎引用的数据对象集合;规则执行队列,用于存放被激活的规则执行实例;静态规则区,用于存放所有被加载的业务规则, 这些规则将按照某种数据结构组织,当工作区中的数据发生改变后,引擎需要迅速根据工作区中的对象现状,调整规则执行队列中的规则执行实例。Java规则引 擎的结构示意图如下图所示。



python规则引擎pyke 规则引擎 java_api

 

 
Java规则引擎工作机制



“动态”的规则执行链,形 成规则的推理机制。这种规则的“链式”反应完全是由工作区中的数据驱动的。

  任何一个规则引擎都需要很好地解决规则的推理机制和规 则条件匹配的效率问题。规则条件匹配的效率决定了引擎的性能,引擎需要迅速测试工作区中的数据对象,从加载的规则集中发现符合条件的规则,生成规则执行实例。1982年美国卡耐基?梅隆大学的Charles L. Forgy发明了一种叫Rete算法,很好地解决了这方面的问题。目前世界顶尖的商用业务规则引擎产品基本上都使用Rete算法。

 

三、开源实现 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-set   name 
 ="fibonacci" 
  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 
 > 
 java.util.* 
 </ 
 import 
 > 
  

  <  application-data  
 identifier 
 ="request" 
 > 
 javax.servlet.http.HttpServletRequest 
 </ 
 application-data 
 > 
  

  <  java:functions 
 > 
  

public static int countAdslByTel(HttpServletRequest request)throws Exception{

       PrdInstDAO prdInstDAO=new PrdInstDAO (request.getParameter(“latnId”));

       Return prdInstDAO.countAdslByTel(request.getParameter(“service_nbr”)) ;

}

  </  java:functions 
 > 
  

  <  rule  
 name 
 ="Prd_1-0-A1-1307" 
  salience 
 ="1" 
  no-loop 
 ="true" 
 > 
  
  <  parameter  
 identifier 
 ="map" 
 >< 
 class 
 > 
 Map 
 </ 
 class 
 ></ 
 parameter 
 > 
  

  <  parameter  
 identifier 
 ="bean" 
 > 
 < 
 class 
 > 
 AcceptReqBean 
 </ 
 class 
 ></ 
 parameter 
 > 
  

  <  java:condition 
 > 
 map.get(“prdId”).equals("1307") 
 </ 
 java:condition 
 > 
  
  <  java:consequence 
 > 
  

Int count=      countAdslByTel(request);

If(map.get(“计费方式”).equals(“宽带上网卡”)){

       If(map.get(“latnId”).equals(“552”)&  &count  >=5){

       bean.setPass(false);

}else

   bean.setPass(true);

}else{

       If(count  <  1 
 ){

bean.setPass(true);

}else{

       bean.setPass(false);

       }

}

</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项目。这个facade对象通过javax.rules API,创建了供RuleExecutionSet和RuleSession使用的一系列对象。它并没有完全包括了Drools引擎API的所有特性和细微差别,但可以作为新手使用API的一个简单例子。
下面的代码片断显示如何在程序中调用drools规则引擎并执行。

 



//   执行规则引擎   
          RulesEngine rulesEngine  =  new 
  RulesEngine(url);
        List rulesResult  =  rulesEngine.executeRules(  new 
  WorkingEnvironmentCallback() 
  
  {

             public   void  initEnvironment(WorkingMemory workingMemory)  throws  FactException  
 {

                      workingMemory.assertObject(..);
                      workingMemory.setApplicationData( " map " ,map);
..
            }  
        }  ); 
 
 
       
  
        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;

    /** *//**
     * 构造方法,装载规则文件
     * 
     * @param rulesFile
     *            绝对规则文件路径
     * @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);
        }
    }

    /** *//**
     * 构造方法,装载规则文件/debug模式
     * 
     * @param rulesFile
     * @param debug
     * @throws RulesEngineException
     */
    public RulesEngine(URL rulesFile,boolean debug) throws RulesEngineException{
        this(rulesFile);
        this.debug=debug;
    }

    /** *//**
     * 构造方法,装载规则文件/debug模式
     * 
     * @param rulesFile
     * @param debug
     * @throws RulesEngineException
     */
    public RulesEngine(String rulesFile,boolean debug) throws RulesEngineException{
        this(rulesFile);
        this.debug=debug;
    }

    /** *//**
     * 执行规则
     * 
     * @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());
        }
    }

}


结束语



  规则引擎技术为管理多变的业务逻辑提供了一种解决方案。规则引擎既可以管理应用层的业务逻辑又可以使表示层的页面流程可订制。这就给软件架构师设计大型信息系统提供了一项新的选择。而 Java 规则引擎在 Java

社区制定标准规范以后必将获得更大发展。 posted on 2006-11-27 22:15 jacky