未分类

基于规则引擎的LS_WAF2.0框架

重庆·洋人街 2008.01

基于规则引擎的LS_WAF2.0框架

1 规则引擎概述

1.1 什么是规则引擎

在说规则引擎之前,有必要先介绍三个概念:人工智能、专家系统、产生式规则。简单而通俗来说,人工智能就是模拟和扩展人的智能的技术科学,如我们熟悉的机器人、语音识别和专家系统等等。专家系统是一个计算机程序系统,只不过他拥有大量的专家知识,可以为我们解决一些特定领域的问题。也就是说,专家系统可以看作是一位以计算机系统的形式存在的专家。专家系统包含很多种类,使用较为广泛的一种叫做产生式规则的专家系统。顾名思义,这种专家系统是以规则推理来驱动的。产生式规则一般可以表示为:如果A成立,那么B成立,相当于VB语言里的if then语句。规则的推理分为两种,一种是正向推理,一种是逆向推理。正向推理主要用推理树,而逆向推理主要用规则栈。下面用笔者本科时做的一个小程序形象的说明正向推理产生式规则专家系统。

清单 治疗轻风热型病人的专家系统

1
2
3
4
规则R1:鼻塞流涕->外感风热
规则R2:毛囊皮根微红肿->轻型
规则R3:轻型∧外感风热->轻风热型
规则R4:轻风热型->散瘌加罐

系统会根据规则R1和R2询问患者,如果患者都具备,则根据规则R3可以推理出患者是轻风热型,再规则R4可以得到治疗方法:散瘌加罐。

也就是说,该系统是通过固定的一些规则,根据正向推理,从而得出问题的最终解决方案。
规则引擎的基本原理就是来源于产生式规则的专家系统。由于本框架前台使用Java语言,因此在本文只介绍Java规则引擎,主要分为三部分:工作内存、静态规则区和规则执行队列。下面是Java规则引擎的结构图。

图1 Java规则引擎工作机制结构图

我们对清单1和图1进行对比,可以发现两者的的工作原理是惊人的相似。Java数据对象可以理解为患者,他具有鼻塞流涕和毛囊皮根微红肿的属性;规则R1到R4存储在静态规则区里;最后,根据前两者推理出规则的执行队列为R1-R2-R3-R4。所以,理解了专家系统,再理解规则引擎就不难了。

###1.2 规则引擎的选择
使用规则引擎会发现有很多的好处,而笔者感受最深的有三个优势:声明式编程、轻量级修改、容易理解。声明式的编程大概是程序员最喜爱的,因为您只需要去做一个简单的声明(也就是英文里的what而不是how)就可以实现可能异常复杂的功能;另外,面对多变的需求,使用规则引擎的修改往往是不需要进行重新的编译和部署升级即可运行;最后一点你会觉得非常的神奇,因为使用规则引擎里的域定义语言可以让一点不懂计算机编程的业务人员极其容易的对规则进行维护。
何时需要规则引擎。尽管规则引擎这么吸引,但是并不是所有的场合都适用。最重要的一点是系统的业务逻辑是经常发生变化的,否则,使用了规则引擎是起不了一点的作用,还给系统增加了不少的负担。再者,这里所说的业务逻辑多是业务规则的判断与选择,您可以把他理解为代码里的if语句,规则引擎绝对不是用于进程管理、数值计算、数据存储那一类的业务。
现今主流的规则引擎工具有很多,笔者比较倾向于使用jboss的drools,原因一是开源,二是比较成熟,现在的最新版本是drools5。

2 LS_WAF框架与LS_WAF2.0框架

2.1 LS_WAF简介

Amdocs China的LS_WAF框架是从表现层框架WAF发展而来,增加了Bea的Tuxedo后台交易中间件,即能达到快速开发的效果,又能实现系统的高效运行,已经在中国的电信行业多次成功实施。

图2 LS_WAF框架图

2.2 使用规则引擎后的LS_WAF

图3 LS_WAF2框架

增加了规则引擎后,对于某些多变的业务处理就变得非常简单了。但是由于还有大部分的业务都在tuxedo交易里,所以规则引擎还不能对这些业务进行控制。不过,对于原来的框架也算是有了一小步的前进。

3 LS_WAF2.0实战

下面通过一个具体的例子,描述drools是怎么应用的。
在联通的BSS系统里,与外围系统VASP有订购关系的实时接口。针对订购的结果要返回信息,如果订购失败,则需返回一个错误码。比如,错误码11表示用户无效,21表示找不到业务代码等等。
然而,BSS系统订购增值业务的结果是通过调用自身的Tuxedo交易完成的,交易并不会返回一个具体的错误码,只会返回一个相应的中文描述,如,用户无效、找不到业务代码等等。这样,IBAS给VASP返回错误码的时候,便需要进行一个转换。表格1是错误中文描述和错误码之间的对应关系:
表格1

中文描述 错误码
用户无效 11
业务代码找不到 21
产品代码找不到 41
其它 50

把上述对应关系用java代码实现如清单1中的resultCodeConversion(String)方法所示:

清单1 返回结果共用类resultCodeConversion(String)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package drools.bss2vasp;

import java.io.InputStreamReader;
import java.io.Reader;

import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.compiler.PackageBuilder;
import org.drools.rule.Package;

public class OrderConfirmCommon {
public static String resultCodeConversion(String result)
{
String resultCode = "";

if (result.equals("用户无效"))
resultCode = "11";
else if (result.equals("业务代码找不到"))
resultCode = "21";
else if (result.equals("产品代码找不到"))
resultCode = "41";
else
resultCode = "50";

return resultCode;
}
}

但是,由于这些所谓的中文描述只是程序员写在交易里,方便报错时进行追踪,所以随时都会修改;另外,由于联通增值业务和其他模块的多变性,错误码经常会出现增加的情况。一旦增改,清单1中的代码即需要马上跟随变动。实际上,很快表格1中的对于关系发生了改变,如表格2所示:

中文描述 错误码
无有效用户 11
业务代码找不到 21
产品代码找不到 41
用户的状态不能受理该业务 12
不能重复订购 51
其它 50

(其中,“不能重复订购”只需要包含即可,其余的都需要严格匹配。)

如此,该接口需要频繁的进行修改、重编译、部署、升级。于是,规则引擎Drools便派上用场了。

针对同样的业务逻辑,在清单1中的返回结果共用类中增加使用了Drools的resultCodeConversionByDrools(String)方法,如清单2所示:

清单2 返回结果共用类resultCodeConversionByDrools(String)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static java.lang.Integer resultCodeConversionByDrools(String result)
{
OrderConfirmRsp rsp = new OrderConfirmRsp();

try
{
Reader source = new InputStreamReader(OrderConfirmCommon.class
.getResourceAsStream("/drools/drl/OrderConfirmSync.drl"));
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl(source);
Package pkg = builder.getPackage();
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage(pkg);
WorkingMemory workingMemory = ruleBase.newStatefulSession();

rsp.setEffectiveDate(result);
workingMemory.insert(rsp);
workingMemory.fireAllRules();
}
catch (Throwable t)
{
t.printStackTrace();
}

return rsp.getResultCode();
}

在清单2中可以看出,代码里没有任何的业务逻辑,只读了一个配置文件OrderConfirmSync.drl。而业务逻辑都包含在该文件里,如清单3所示:

清单3 drl规则文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package drools.bss2vasp

import drools.bss2vasp.OrderConfirmRsp;

rule "Conversion1"
salience 10
when
rsp : OrderConfirmRsp(resultMessage == "无有效用户")
then
rsp.setResultCode(new Integer(11));
end

rule "Conversion2"
salience 10
when
rsp : OrderConfirmRsp(resultMessage == "业务代码找不到")
then
rsp.setResultCode(new Integer(21));
end

rule "Conversion3"
salience 10
when
rsp : OrderConfirmRsp(resultMessage == "产品代码找不到")
then
rsp.setResultCode(new Integer(41));
end

rule "Conversion4"
salience 10
when
rsp : OrderConfirmRsp(resultMessage == "用户的状态不能受理该业务")
then
rsp.setResultCode(new Integer(12));
end

rule "Conversion5"
salience 10
when
rsp : OrderConfirmRsp(resultMessage matches
"(.?)+不能重复订购+(.?)+")
then
rsp.setResultCode(new Integer(51));
end

rule "Conversion6"
salience 11
when
rsp : OrderConfirmRsp()
then
rsp.setResultCode(new Integer(50));
end

规则文件非常通俗易懂,其中由于“不能重复订购”不是完全匹配,需要用到正则表达式,其余的一看都能看懂。以后,如果增加了一个规则,只需要改该规则的配置文件即可,无需重新编译部署,更省去了升级的繁琐流程。

下面的清单4和5分别是上面用到的结果返回类和JUnit测试类。

清单4 结果返回类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package drools.bss2vasp;

public class OrderConfirmRsp implements java.io.Serializable {
private java.lang.String resultMessage;
private java.lang.Integer resultCode;

public OrderConfirmRsp() {
}

public java.lang.String getResultMessage() {
return resultMessage;
}

public void setEffectiveDate(java.lang.String resultMessage) {
this.resultMessage = resultMessage;
}

public java.lang.Integer getResultCode() {
return resultCode;
}

public void setResultCode(java.lang.Integer resultCode) {
this.resultCode = resultCode;
}
}

清单5 测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package drools.test;

import junit.framework.TestCase;
import drools.bss2vasp.OrderConfirmCommon;

public class OrderConfirmCommonTest extends TestCase{
public void testResultCodeConversionByDrools()
{
assertEquals(new Integer(11),OrderConfirmCommon.resultCodeConversionByDrools("无有效用户"));
assertEquals(new Integer(21),OrderConfirmCommon.resultCodeConversionByDrools("业务代码找不到"));
assertEquals(new Integer(41),OrderConfirmCommon.resultCodeConversionByDrools("产品代码找不到"));
assertEquals(new Integer(12),OrderConfirmCommon.resultCodeConversionByDrools("用户的状态不能受理该业务"));
assertEquals(new Integer(51),OrderConfirmCommon.resultCodeConversionByDrools("不能重复订购"));
assertEquals(new Integer(51),OrderConfirmCommon.resultCodeConversionByDrools("对不起,不能重复订购该产品"));
assertEquals(new Integer(51),OrderConfirmCommon.resultCodeConversionByDrools("1不能重复订购"));
assertEquals(new Integer(50),OrderConfirmCommon.resultCodeConversionByDrools("不能订购"));
}

测试通过,如下图所示:

输入图片说明

4 结语

如今,框架在J2EE里大行其道,提起表现层大家都想到struts、webwork2、JSF等等;提起持久层大家都知道hibernate、ibatus、ejb的实体bean云云;但是关注于业务逻辑的却很少,spring也只是结构性的一种应用。可能各个行业的业务差别太大,还很难有一个通用性的业务层框架可以彻底解决,也许drools的出现,是能或多或少的填补这个方面的空白。

分享到