简介
业务应用的需求总是随着业务环境的变化趋势而不断地改变。决策很少是一成不变的,并且竞争压力要求业务逻辑的设计和实现具有灵活性,以快速地适应不断变化的需求。通常,对业务逻辑的更改必须由开发人员来完成,然后进行多次彻底的测试,而这将是一个很耗时的过程。在应用程序的修改工作完成后,需要将其重新部署到服务器,需要留出预定的停机时间,以防应用程序对用户不可用。
对于这个问题,更好的解决方案是通过应用程序之外的一组规则来实现某些业务决策。这些规则并没有被编译到应用程序中,而是在运行时读取并应用。通过这种方式,无需更改代码或者停止正在运行的应用程序就可以改变这些规则。
WebLogic Portal包括一个基本的规则引擎,用于使WebLogic Platform应用程序从规则获益。尽管该引擎不是功能完备的产品,但我们将展示如何将其与WebLogic Integration Business Processes (JPDs)一起使用来为业务逻辑提供一种灵活且动态的实现机制,从而无需只为了修改规则而重新部署应用程序。
首先我们来看一下将在全文中使用的示例应用程序,然后介绍如何将规则引擎注入到WLI流程中以实现业务逻辑。然后,我们将更仔细地研究这些规则本身以及如何为业务逻辑定义这些规则,最后将描述在运行中的系统中更改业务规则所使用的机制。
示例应用程序
我们将开发一个示例交易应用程序作为在业务流程中使用规则的例子。该交易应用程序是一个金融交易流程的简化版本,该流程使用调用规则引擎的JPD业务流程而构建。该示例应用程序采用了不同的有价证券交易集合,并根据一组由业务定义的规则将其分组成交易块以便执行,或许是为了减少佣金。当然,这里给出的应用程序并不完整,但它已经足以展示如何在现实世界应用程序中使用规则引擎。有完整的源代码可供下载,其中的readme文件提供了构建和运行该应用程序的说明。
在解释如何开发这样的应用程序之前,我们先通过对门户规则引擎的一些特性的简要概括来了解其工作方式。这里假定读者熟悉一般的规则技术。
规则引擎及其工作方式
图1说明了规则引擎的基本情况。该引擎根据一组规则来处理初始的事实集,而这些规则由引擎从外部库中获得。初始事实用于为该引擎填充工作内存。由规则来对工作内存中的事实进行评估,如果满足某条规则的条件,则将执行对应的动作。通常,一个规则动作将向工作内存中添加一条新的事实,并重复该流程直到应用完所有的规则。然后通过可选的过滤器来选择特定类的对象以返回给调用方。可以通过Controls界面访问规则引擎,还可以用它来设置属性(比如规则集文件的位置)。
图1:该规则引擎是一个由控件包装的EJB。储存库中的规则反复地应用于工作内存中的事实,以获得新的事实。在无法进行继续推理的情况下,对工作内存进行过滤以返回感兴趣的项目。
从WLI流程调用规则引擎
让我们从被实现为JPD的交易业务流程开始,来看看如何添加对规则引擎的调用。要在WLI流程中添加规则,可以使用作为WebLogic Portal的一部分而提供的Rules Executor Control(规则执行器控件)。对于本例,我们只使用该控件中所提供的方法和特性的一个子集。关于规则控件的附加说明文档可以在参考资料部分找到。
此处假定开发人员使用WebLogic Workshop集成开发环境来创建新的流程应用程序。然后可在该应用程序中创建一个流程项目。因为默认情况下门户控件在流程项目中不可用,所以需要将这些控件和规则引擎的EJB导入到应用程序。然后,将控件输入和输出插入到JPD中。在WLI流程中使用门户规则引擎的基本步骤如下:
导入规则引擎到应用程序:
在应用程序中包含规则引擎。
将p13n_controls库添加到应用程序。
处理输入和输出:
为输入和结果添加变量。
创建一个Rules Executor控件。
在WLI流程中添加一个Control Send With Return节点。
为创建初始数据编写Java代码。
添加一个流程节点以对结果进行迭代。
创建规则集。
后面我们将更详细地讨论其中的每一个步骤。
在应用程序中包含规则引擎
规则引擎包含在下面的文件中:
/weblogic81/p13n/lib/p13n_ejb.jar
要在应用程序中包含该引擎,请右击Workshop集成开发环境中的Modules文件夹,并选择Add Module。导航到该jar文件,并选择Open。
将p13n_controls库添加到应用程序
要使得门户规则控件在应用程序中可用,请右击Workshop集成开发环境中的Libraries文件夹,并选择Add Library。该控件位于:
</weblogic81/p13n/lib/p13n_controls.jar
导航到此文件,单击 Open 按钮。
为输入和结果添加变量
这里使用的Rules Executor控件方法需要一个对象数组作为输入并返回一个结果的迭代器。在Workshop集成开发环境中为这些值创建变量,这样我们就可以在下一步中通过图形用户界面来创建控件。要完成该任务,请为Data Palette中的变量单击Add按钮,键入输入变量的名称,并键入Java类型java.lang.Object[]。使用同样的方式创建Java类型为java.util.Iterator的输出变量。
创建一个Rules Executor控件
要创建规则控件,请单击Data Palette中控件的Add按钮。从菜单中选择Portal Controls -> Rules Executor。为控件键入名称,并按下Create按钮。
在WLI流程中添加一个Control Send With Return节点
将刚刚创建的控件拖放到流程中以创建一个控件节点来实际调用规则引擎。在示例中,我们将使用控件的evaluateRuleSet()方法。从Send Data面板中,选择前面为方法的输入参数而创建的输入变量。使用Receive Data面板选择返回变量来获取规则执行的结果。在Property Editor窗口中为控件属性键入相应的值。
为创建初始数据编写Java代码
在创建了输入变量后,我们还没有对它赋值,所以需要编写代码来完成该任务。这个变量是一个Java对象数组,它提供了输入到规则条件中的初始事实。可以创建一个新的Perform节点来初始化该数组,或者通过使用Source View在Control Send节点中添加代码来设置该变量的值。
添加一个流程节点以对结果进行迭代
Rules Executor控件的每一个计算方法都将返回一个结果的迭代器。编写代码,使用该值实现对规则执行结果的迭代。如果没有指定过滤器类,这个迭代器将返回规则引擎工作内存中的所有值。其中包括原始输入以及任何在执行满足条件的规则的动作时添加到工作内存中的值。对于添加的对象,迭代器返回一个Result类的对象,该类的getObject()方法可以返回在规则动作中所添加的实际对象。
创建规则集
使用XML编辑器,在/META-INF/data目录中创建一个扩展名为.rls的文件。规则通常添加到子目录rulesets中。
业务逻辑规则
我们刚刚展示了如何在业务流程中插入规则引擎。现在让我们来看看如何利用该规则引擎以及如何编写映射到业务规则的规则。
规则包括两个部分:应用该规则时必须为真的条件,以及当条件满足时将执行的动作。因此,要在应用程序中使用规则,设计人员必须首先定义哪些对象和属性在测试规则条件时对规则编写者是可见的。规则引擎允许在一个条件中调用任意多个方法。这种构造方式便于定义JavaBean作为组成初始事实集合的对象,规则引擎使用这些初始事实来进行推理。可以使用bean的 get方法来获得条件测试的值。
规则所引用的Java对象需要从创建它的WLI流程以及从规则引擎本身中都可见。这就避免了这些对象与流程JPD位于同一包中,更确切地说,它们应该被创建于作为同一应用程序的一部分的Java项目中。然后,这些对象可以通过package.class标记在规则文件(.rls)和流程JPD中引用。
在我们的交易示例中,将把不同的交易分组以便可以成块执行。为实现该目标,我们定义两个bean来表示相关的对象。第一个是Trade bean,它表示单个的交易订单。这个bean的属性表示交易的份额、股份数目以及所期望的价格等。任何对于决定交易所属的块来说可能有用的值,都应该作为这个具有公有get方法的bean的属性,以便能够在规则中使用它。第二个bean是Block bean,通过它可以存储所有根据某个属性集聚合在一起的不同交易。这个bean的属性包括规则中任何可用于判定块大得足以执行订单的的信息。这些属性可以是平均价格、交易的总美元数或总的份额数等等。
为了在我们的应用程序中实现块功能,首先使用规则来定义某项交易是否只需要执行其自身就足够了(也就是说,它是仅包含单个交易的块),或者如果不是这样的话,那么应该使用什么属性将其与其他交易聚集以形成一个块。在一项交易聚集到适当的块中后,就会第二次调用规则引擎来判断该块是否完成。例如,假设我们想要得到这样的规则:
规则1:任何5,000股及以上的单项交易应该作为一个块并予以执行。
规则2:由同一个投资管理者定购的具有相同标记的交易应该聚集在一起。
规则3:总价值超过,000的块应该予以执行。
调用与规则条件中的对象相关联的方法很容易,如下面的示例所示,这是规则1的条件:
<cr:conditions>
<greater-than-or-equal-to>
<instance-method>
<variable>
<type-alias>Beans.Trade</type-alias>
</variable>
<name>getQuantity</name>
<!-- getQuantity (and any other bean property) takes
no arguments. If it did, they
would go here
<arguments>...</arguments>
-->
</instance-method>
<literal:integer>
5000
</literal:integer>
</greater-than-or-equal-to>
</cr:conditions>
在这个示例中,如果在我们的事实中有一个Trade对象,那么规则引擎就会调用它的getQuantity()方法并且将结果与整型5000进行比较。如果它大于或等于5000,则该条件为真。
规则的第二部分是条件满足时执行的动作的列表。最常见的动作是:创建一个新对象,把它添加到规则引擎用来评估条件的事实集中。规则引擎继续对规则进行迭代,直到无法从事实中得出更多的推理;向动作添加新对象会导致另一轮的条件评估循环。正如我们将要看到的那样,可以创建任意类型的对象,并定义对应用程序具有特定意义的各种类型。这里的技巧是,应用程序设计者可以定义一组足够丰富的动作,以包含那些可由规则编写者调用以满足各种业务需求的任务。
在我们的交易应用程序示例中,所有动作都会创建将添加到由规则引擎使用的工作集中的新对象。有些规则向该集合中添加简单的String对象。这些对象表示了从原始事实中演绎出来的中间事实,它们可以在规则引擎中得到进一步的推理,但流程JPD不会以任何形式解释它们。其他的规则将创建Beans.Action类的对象。这些对象包括当规则条件满足时流程将执行的实际命令。流程JPD和支持类将实施已知的动作命令来聚集交易并执行块交易。在这个简单的示例中,实际上只有两个已知的命令:创建(并执行)订单、使用指定的属性聚集一项交易。前面规则2的动作是使用属性symbol和manager来进行聚集,该动作如下:
<cr:actions>
<new-instance>
<type-alias>Beans.Action</type-alias>
<arguments>
<literal:string>symbol, manager</literal:string>
</arguments>
</new-instance>
</cr:actions>
响应该动作,流程JPD及其支持类为当前交易查询symbol和投资manager,找出具有相同的symbol和投资manager的未执行交易,并将这些交易聚集到相同的块。
在完成一项交易的聚集后,将从第二个Rules Executor控件再次调用规则引擎,以评估规则,决定是否应该执行产生的块交易。根据业务规则3,该规则如下:
<cr:conditions>
<greater-than >
<instance-method>
<variable>
<type-alias>Beans.Block</type-alias>
</variable>
<name>getAmount</name>
</instance-method>
<literal:float>
50000.00
</literal:float>
</greater-than >
</cr:conditions>
<cr:actions>
<new-instance>
<type-alias>Beans.Action</type-alias>
<arguments>
<literal:string>create</literal:string>
</arguments>
</new-instance>
</cr:actions>
这一次,我们分析Beans.Block对象,获取amount属性并与阈值进行比较。如果该条件满足,则使用create命令向工作集中添加一个Beans.Action对象,这是通知流程执行该块订单的信号。
让我们仔细分析一下流程JPD。下面有用于调用规则引擎的Control Send节点的代码。正如我们可以看到的,该节点使用一个Rules Executor控件来评估规则集,该控件返回一个迭代器。通过其属性(没有给出),控件将过滤结果,仅返回Beans.Action类的对象。通过这些对象,代码将提取动作命令并执行所请求的动作。正如前面所提到的,如果动作是聚集该交易,则流程将使用更新后的块作为输入,对规则引擎开始第二次调用。通过执行适当的动作,对结果进行第二次迭代循环。
public void rulesExecutorControlEvaluateRuleSet()
throws Exception
{
// Execute the Rules using facts as the input
//#START: CODE GENERATED - PROTECTED SECTION - you can safely
// Add code above this comment in this method. #//
// Input transform
// Return method call
this.results =
rulesExecutorControl.evaluateRuleSet(this.facts);
// Output transform
// Output assi