最近组内的一个项目需要"业务规则的可订制性"功能,通过搜索,我们找到了“java规则引擎”这样一个工具。仔细看了看Drools商务逻辑框架的介绍,发现这个框架只能适用于扁平的一级判断,对于复杂的树形的分层的逻辑不能直接的适用。详见文中的例子。当然,我们可以将树形的逻辑判断流程压缩成很多并列的规则。但是,这样又会出现条件的重复判断,即资源浪费的问题。研究中...
(注:转载文章版权属于转载出处)
Drools- 商务逻辑框架的选择
摘要:
大多数网络及企业级Jave应用可以分为三部分:和用户交互的前端,和后端系统(比如数据库)交互的服务层和这两部分之间的商务逻辑层。通常使用框架(像 Struts, Cocoon, Spring, Hibernate, JDO, 和实体Beans)可以实现前端和后端的功能,但对于商务逻辑层却没有一个标准的构建方法。这篇文章建议使用Drools规则引擎作为框架来解决问题。版权声明:任何获得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文件都被引用了。
![image](http://www.matrix.org.cn/resource/upload/forum/2005_12_12_230601_HHjSitMOHT.gif)
图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](http://www.matrix.org.cn/resource/upload/forum/2005_12_12_230733_CsZnSMdDeO.gif)
图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](http://www.matrix.org.cn/resource/upload/forum/2005_12_12_231602_YUwJXgaqhC.gif)
图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()));
}