个人观点:文章比较老,本质上是责任链模式(不纯)基于Spring的实现,从而让责任链的构建更加灵活,虽然现在基于注解的配置逐渐成为主流,不过作者的设计思想还是值得借鉴。
摘要
这篇文章解释了在J2EE应用中规则引擎及声明性业务逻辑的优点,并且描述如何为流行的Spring框架开发简单的规则引擎。
任何大一点的软件项目都包含了许多叫做业务逻辑的东西。业务逻辑的准确描述还是有争议的。在为典型应用软件的生成的大量代码中,到处都是为如订单处理、武器控制系统、图形绘制等功能工作的零碎代码。这些代码与其他如处理持久化、日志、事务、语言偏好、框架特性及其他现代企业级应用有明显不同。
业务逻辑通常与其他代码块紧密的混和在一起。当重量级的侵入式框架(如EJB)被使用时,区别业务逻辑与框架生成的代码就变得非常困难。
有一个软件需求在需求定义文档很难准确描述,却拥有使软件项目成功或失败的能力:适应性,这是用来衡量软件响应业务变更容易程度的标准。
现代企业要求响应快速及灵活,他们对企业软件也有同样的要求。可能你今天辛苦实现的业务规则在明天就被废弃了而且要求你根据变更快速而准确的改变。当你的包含业务逻辑的代码隐藏在大量其他代码中时,修改就变得缓慢、痛若且易出错。
在今天的企业级软件中没有奇迹,比较流行的是规则引擎和各种业务过程管理(BPM)系统。如果你看一下市场上的宣传,这类工具都承诺一件事:保存在仓库中的捕获业务逻辑的圣杯能够清晰的分离且由自己维护,并随时准备让你现有的应用来调用。
虽然商业的规则引擎和BPM系统有许多优点,但也有不少缺点。最大的缺点就是价格,通常很容易就达到7位数。另一个就是除了主要的行业规范和众多记在纸上的标准外缺乏事实上的标准。而且随着越来越多的软件项目采用敏捷、轻量级的快速开发方法,这些重量级的工具变得跟不上潮流。
在这篇文章中,我们建立了一个简单的规则引擎,一方面平衡系统与业务逻辑的分离,另一方面由于基于目前流行且强大的J2EE框架因而不需要承受商业软件的复杂性与不协调性。
J2EE世界中的Spring时代
在企业级软件的复杂性变得不能忍受及业务逻辑问题越来越重要时,Spring及类似的框架产生了。可以断定Spring在以后很长一段时间内是企业级Java中的佼佼者。Spring提供了很多工具及少量代码约定使J2EE的开发更面向对象,更容易也更有趣。
Spring的核心是IOC原则,这是一个奇特而超负荷的名字,但包含下面的简单想法:
- 功能代码需要分开到更小的可管理片断
- 这些片断是简单的,标准的JavaBean(简单的Java类拥有但不包含全部的JavaBean规范)
- 你不需要参与管理这些Bean(如创建、销毁、设置依赖)
- 相反Spring容器通过上下文定义来为你做这些
Spring也提供了很多其他特性,如完整而强大的MVC框架,简便的JDBC开发包装及其他框架,但那些主题已经超出这篇幅文章的讨论范围。
在我描述需要什么来创建基于Spring应用的简单规则引擎之前,让我们想一下为什么这是一种好的想法。
规则引擎设计有两点有趣的特性使其更有价值:
- 首先,从应用领域分离了业务逻辑代码。
- 其次,可配置性意味着业务规则的定义及其使用的顺序被存储在应用的外部,这样就可以由规则创建人员来控制而不是应用的使用者或者开发人员了。
Spring为规则引擎提供了一个好的方法。一个良好编码的Spring应用的强组件化的设计会使你的代码变成更小的、可管理的分散片断,这样就更易在Spring的上下文定义中配置。
基于Spring的规则引擎的设计
我们在Spring控制的JavaBean基础上开始设计,这里我们叫做规则引擎组件。我们来定义下面两种我们可能需要的组件类型:
- 操作:在应用逻辑中确定用来做什么的组件
- 规则:在一系列行为的逻辑流中做出决定的组件
我们都是面向对象设计的追随者,下面的基类建立了所有我们的组件需要通过参数被其他组件调用的基本功能:
public abstract class AbstractComponent {
public abstract void execute(Object arg) throws Exception;
}
当然基类是抽象的因为我们根本不需要这样的实例。AbstractAction的代码扩展了基类来实现其他具体的操作:
public abstract class AbstractAction extends AbstractComponent {
private AbstractComponent nextStep;
public void execute(Object arg) throws Exception {
this.doExecute(arg);
if (nextStep != null) {
nextStep.execute(arg);
}
}
protected abstract void doExecute(Object arg) throws Exception;
public void setNextStep(AbstractComponent nextStep) {
this.nextStep = nextStep;
}
public AbstractComponent getNextStep() {
return nextStep;
}
}
你可以看到,AbstractAction做两件事:首先他保存在规则引擎中被激活的下一个组件的定义;其次在他的execute()方法中,调用被具体类实现的doExecute()方法,在doExecute()返回后,如果存在下一个组件则调用他。我们的AbstractRule也相当简单:
public abstract class AbstractRule extends AbstractComponent {
private AbstractComponent positiveOutcomeStep;
private AbstractComponent negativeOutcomeStep;
public void execute(Object arg) throws Exception {
boolean outcome = makeDecision(arg);
if (outcome) {
positiveOutcomeStep.execute(arg);
} else {
negativeOutcomeStep.execute(arg);
}
}
protected abstract boolean makeDecision(Object arg) throws Exception;
// 省略getter和setter
}
在其execute()方法中,AbstractAction调用由子类实现的makeDecision()方法,然后根据方法的返回值,调用组件定义的肯定或否定结果的方法。在我们介绍了SpringRuleEngine类后我们的设计就基本完成了:
public class SpringRuleEngine {
/** 指定第一个规则逻辑 */
private AbstractComponent firstStep;
public void processRequest(Object arg) throws Exception {
firstStep.execute(arg);
}
public void setFirstStep(AbstractComponent firstStep) {
this.firstStep = firstStep;
}
}
这就是我们规则引擎主类的全部:定义第一个业务逻辑中的组件及开始执行的方法。但是请稍等,在哪里绑定我们的类使之可以工作呢?下面你就可以看到如何利用Spring来帮助我们完成工作的方法了。
在操作中的基于Spring的规则引擎
让我们看一下这个框架如何工作的具体实例吧。想象下面的用例:我们需要开发负责贷款申请的应用程序。我们需要满足下面的条件:
- 检查应用的完整性否则驳回
- 检查应用是否来自我们授权处理业务的应用。
- 检查申请者的月收支比是否满足我们的要求。
- 输入的申请通过我们不知道实现细节的持久服务被存储在数据库中,我们只知道他的接口(可能这个开发被外包到印度了)
- 业务规则是可以改变的,这也是为什么需要规则引擎的设计了。
首先,设计一个表示贷款申请的类:
public class LoanApplication {
public static final String INVALID_STATE = "Sorry we are not doing business in your state";
public static final String INVALID_INCOME_EXPENSE_RATIO = "Sorry we cannot provide the loan given this expense/income ratio";
public static final String APPROVED = "Your application has been approved";
public static final String INSUFFICIENT_DATA = "You did not provide enough information on your application";
public static final String IN_PROGRESS = "in progress";
public static final String[] STATUSES =
new String[] {
INSUFFICIENT_DATA, INVALID_INCOME_EXPENSE_RATIO, INVALID_STATE, APPROVED, IN_PROGRESS
};
private String firstName;
private String lastName;
private double income;
private double expences;
private String stateCode;
private String status;
public void setStatus(String status) {
if (!Arrays.asList(STATUSES).contains(status)) {
throw new IllegalArgumentException("invalid status:" + status);
}
this.status = status;
}
// 略去getter和setter方法
}
我们使用的持久服务拥有如下接口:
public interface LoanApplicationPersistenceInterface {
public void recordApproval(LoanApplication application) throws Exception;
public void recordRejection(LoanApplication application) throws Exception;
public void recordIncomplete(LoanApplication application) throws Exception;
}
我们迅速开发一个什么也不做只是用来满足接口约定的MockLoanApplicationPersistence类来欺骗接口。
public class MockLoanApplicationPersistence implements LoanApplicationPersistenceInterface {
@Override
public void recordApproval(LoanApplication application) throws Exception {
}
@Override
public void recordRejection(LoanApplication application) throws Exception {
}
@Override
public void recordIncomplete(LoanApplication application) throws Exception {
}
}
我们使用下面的SpringRuleEngine类的子类来加载Spring上下文并开始处理:
public class LoanProcessRuleEngine extends SpringRuleEngine {
public static final SpringRuleEngine getEngine(String name) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("SpringRuleEngineContext.xml");
return (SpringRuleEngine) context.getBean(name);
}
}
这时候,我们已经有了代码框架了,因此是时候写JUnit测试了,代码如下。其中包含一些假设:我们期望公司仅在两种州运作,德克萨斯和密歇根。而且我们只接受收支比在70%或更好的人的贷款申请。
public class SpringRuleEngineTest {
@Test
public void testSuccessfulFlow() throws Exception {
SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("springRuleEngine");
LoanApplication application = new LoanApplication();
application.setFirstName("John");
application.setLastName("Doe");
application.setStateCode("TX");
application.setExpences(4500);
application.setIncome(7000);
engine.processRequest(application);
Assert.assertEquals(LoanApplication.APPROVED, application.getStatus());
}
@Test
public void testInvalidState() throws Exception {
SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("springRuleEngine");
LoanApplication application = new LoanApplication();
application.setFirstName("John");
application.setLastName("Doe");
application.setStateCode("OK");
application.setExpences(4500);
application.setIncome(7000);
engine.processRequest(application);
Assert.assertEquals(LoanApplication.INVALID_STATE, application.getStatus());
}
@Test
public void testInvalidRatio() throws Exception {
SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("springRuleEngine");
LoanApplication application = new LoanApplication();
application.setFirstName("John");
application.setLastName("Doe");
application.setStateCode("MI");
application.setIncome(7000);
application.setExpences(0.80 * 7000); //too high
engine.processRequest(application);
Assert.assertEquals(LoanApplication.INVALID_INCOME_EXPENSE_RATIO, application.getStatus());
}
@Test
public void testIncompleteApplication() throws Exception {
SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("springRuleEngine");
LoanApplication application = new LoanApplication();
engine.processRequest(application);
Assert.assertEquals(LoanApplication.INSUFFICIENT_DATA, application.getStatus());
}
}
显然单元测试会失败因为我们还没有实现任何的逻辑。然而,随着项目的进展,越来越多的测试通过,最后JUnit测试就全部通过了。 让我们继续操作和规则的实现。我们希望操作与持久服务交互因此我们需要一个通用基类:
public abstract class AbstractPersistenceAwareAction extends AbstractAction {
private LoanApplicationPersistenceInterface persistenceService;
public void setPersistenceService(LoanApplicationPersistenceInterface persistenceService) {
this.persistenceService = persistenceService;
}
public LoanApplicationPersistenceInterface getPersistenceService() {
return persistenceService;
}
}
在我们的进程中第一个请求贷款申请的业务规则已经完成:
public class ValidApplicationRule extends AbstractRule {
/**
* 决策函数
*
* @param arg
* @return
* @throws Exception
*/
protected boolean makeDecision(Object arg) throws Exception {
LoanApplication application = (LoanApplication) arg;
if (application.getExpences() == 0 ||
application.getFirstName() == null ||
application.getIncome() == 0 ||
application.getLastName() == null ||
application.getStateCode() == null) {
/*
* 数据不够充分
*/
application.setStatus(LoanApplication.INSUFFICIENT_DATA);
return false;
}
return true;
}
}
注意这个类的一些有趣的现象:他是完全自包含的。他可以由自己实例化或者任何外部的应用容器,而类中的逻辑可以独立的开发和测试。这些现象使类成为基于规则应用的完美的程序块。
现在是时候通过Spring容器来绑定对象了。就象你在单元测试中看到的一样,我们使用LoanProcessRuleEngine
类作为入口指向请求名为springRuleEngine的Bean的规则引擎。下面是这个Bean如何在SpringRuleEngineContext.xml
定义:
<!-- 规则引擎处理器 -->
<bean name="springRuleEngine" class="org.zhenchao.SpringRuleEngine">
<property name="firstStep">
<ref bean="validApplicationRule"/>
</property>
</bean>
这个Bean简单地指明ValidApplicationRule
为业务处理的第一个步骤。这个组件如下定义:
<!-- 数据充分性验证 -->
<bean name="validApplicationRule" class="org.zhenchao.ValidApplicationRule">
<!-- 验证通过处理句柄 -->
<property name="positiveOutcomeStep">
<ref bean="validStateRule"/>
</property>
<!-- 验证失败处理句柄 -->
<property name="negativeOutcomeStep">
<ref bean="rejectionAction"/>
</property>
</bean>
你可以看到,规则自身也在Spring上下文中定义:如果贷款申请是合法的,应用会检查是否正确的州;否则控制传递给RejectionAction
。RejectionAction
也很简单:
public class ProcessRejectionAction implements AbstractPersistenceAwareAction {
protected void doExecute(Object arg) throws Exception {
LoanApplication application = (LoanApplication) arg;
if (LoanApplication.INSUFFICIENT_DATA.equals(application.getStatus())) {
this.getPersistenceService().recordIncomplete(application);
} else {
this.getPersistenceService().recordRejection(application);
}
}
}
它在Spring上下文中如下定义(注意引用了被伪类欺骗的持久服务):
<!-- 拒绝操作处理 -->
<bean name="rejectionAction" class="org.zhenchao.ProcessRejectionAction">
<property name="persistenceService">
<ref bean="loanApplicationPersistenceService"/>
</property>
</bean>
<!-- 持久化服务 -->
<bean name="loanApplicationPersistenceService" class="org.zhenchao.MockLoanApplicationPersistence"/>
我们的下一个业务规则检查是否贷款申请来自合法的州:
public class ValidStateRule extends AbstractRule {
private List validStates;
/**
* 决策函数
*
* @param arg
* @return
* @throws Exception
*/
protected boolean makeDecision(Object arg) throws Exception {
LoanApplication application = (LoanApplication) arg;
if (validStates.contains(application.getStateCode())) {
/*
* 验证通过
*/
return true;
}
/*
* 验证失败
*/
application.setStatus(LoanApplication.INVALID_STATE);
return false;
}
public void setValidStates(List validStates) {
this.validStates = validStates;
}
}
有趣的是,我们的代码并不知道哪些州是合法的。这些业务信息由上下文来定义:
<!-- 州合法性验证 -->
<bean name="validStateRule" class="org.zhenchao.ValidStateRule">
<property name="validStates">
<list>
<value>TX</value>
<value>MI</value>
</list>
</property>
<!-- 验证通过处理句柄 -->
<property name="positiveOutcomeStep">
<ref bean="validIncomeExpenseRatioRule"/>
</property>
<!-- 验证失败处理句柄 -->
<property name="negativeOutcomeStep">
<ref bean="rejectionAction"></ref>
</property>
</bean>
Spring的内建功能再一次让我们从代码中抽取出逻辑流程和参数数据,仅在外部配置就可以了。
如你所见,Spring应用容器为我们的类提供了所有必须的绑定。在启动时,Spring创建所有必须的对象并设置相关的依赖。在运行时,我们的业务规则通过定义在Spring上下文而不是代码中的逻辑和参数数据来执行。
贷款申请示例的其他部分可以用相同方法来配置和开发。完整的源程序及对应的Spring配置可以在资源中找到。
总结
在这篇文章中,我演示了通过Spring帮助你开发基于规则应用的众多方法中的一部分。你也可以使用其内置的AOP来支持在你的规则和操作中混合日志及事务代码而不会污染你先前的业务逻辑。
SPRING应用上下文是可以重新加载的。应用可以修改业务规则和参数(通过修改XML文件)并在运行中重新加载上下文。想象这么做的GUI应用。这么做可以提供与价格为数百万的商业的规则引擎系统的类似功能。 很期望这篇文章可以帮助你尝试一些新鲜而以令人振奋的方法来使你的代码在将来更加有效。
关于作者
Mikhail Garber是居住在达拉斯的独立技术顾问,拥有超过12年的企业级软件开发的经验。尤其擅长Java/J2EE、数据库、消息和开源方案。已经有很多如Mary Kay Cosmetics, Boeing Defense and Space, Verizon Wireless, the US government, Lockheed Martin及其他的组织接受他的指导。
查看原文 中文翻译 源码下载