Using the WebLogic Portal Rules Engine to Implement Dynamic Business Logic in a WebLogic Integration Process, Part 1
Pages: 1, 2

Rules for Business Logic

We've just showed you how to insert a rules engine into a business process. Now let's look at how to utilize the rules engine and how to author rules that map to the business rules.

Rules consist of two parts: conditions that must be true in order for the rule to apply, and the actions that will be performed when the conditions are met. So, to use rules in an application, the designer must first define what objects and properties will be made visible to the rules author in order to test rule conditions. The rules engine allows arbitrary methods to be invoked from within a condition. It is a useful construct to define JavaBeans as the objects that form the set of initial facts that the rules engine uses to draw inferences. The get-methods for the bean are used to retrieve the values for testing in conditions.

A Java object referenced by rules needs to be visible both from the WLI process that creates it and from the rules engine itself. This fact precludes these objects from being in the same package as the process JPD; rather, they should be created in a Java project that's part of the same application. The objects can then be referenced in both the rules (.rls) file and the process JPD by the package.class notation.

In our trading example, we want to group a set of individual trades for execution as a block. To accomplish this, we define two beans to represent the relevant objects. The first is a Trade bean, which represents a single trade order. The properties of this bean are the symbol of the shares to be traded, the number of shares, the desired price and so forth. Any value that might be useful in deciding which block a trade belongs to should be made a property of the bean with a public get-method so that it is available within a rule. The second bean is a Block bean, which is used to store all of the individual trades grouped together according to some set of attributes. The properties of this bean include anything that is useful in rules to determine when the block is sufficiently large to execute the order. Properties are the average price, total dollar amount of the trade or aggregate number of shares, etc.

For the blocking function of our trading application, rules are used first to define if an individual trade is sufficient by itself to be executed (that is, it is a block with just a single trade), or if not, what properties should be used to aggregate it with other trades to form a block. After a trade has been aggregated into its appropriate block, the rules engine is invoked a second time to determine if the block is complete. As an example, let's assume we want our blocking rules to be:

  • Rule 1: Any single trade of 5,000 shares or more should be a block and executed.
  • Rule 2: Trades with the same symbol ordered by the same investment manager should be aggregated.
  • Rule 3: A block whose aggregate value is more than $50,000 should be executed.

Calling a method associated with the object in the conditions of a rule is easy, as shown in this example, which is the condition for Rule 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>

In this example, if there is a Trade object in our facts, the rules engine calls the getQuantity() method on it and compares the result to the integer 5000. If it is greater than or equal to 5000, the condition is true.

The second part of a rule is the list of actions that are performed when the conditions have been satisfied. The most common action is to create a new object and add it to the set of facts that the rules engine is using to evaluate conditions. The rules engine continues to iterate over the rules until no further inferences can be drawn from the facts; adding a new object in an action results in another spin through condition evaluation. Any type of object can be created, and different types can be defined to have meaning special to the application, as we will see later. The trick here is for the application designer to define a set of actions rich enough to cover the range of tasks that can be invoked by the rules author to meet a variety of business needs.

In our trading application example, all actions create new objects that are added to the working set used by the rules engine. Some rules add simple String objects to the set. These objects represent intermediate facts that have been deduced from the original facts; they drive further inferences in the rules engine but are not interpreted by the process JPD in any way. Other rules create objects of class Beans.Action. These objects contain the actual command that the process is to perform when the rule conditions are met. The process JPD and support classes implement the known action commands to aggregate trades into blocks and execute the block trades. In this simple example, there are really only two known commands: create (and execute) an order, and aggregate a trade using the specified attributes. The action for Rule 2 above is to aggregate using the properties "symbol" and "manager," and the action is written:

<cr:actions>
  <new-instance>
    <type-alias>Beans.Action</type-alias>
    <arguments>
      <literal:string>symbol, manager</literal:string>
    </arguments>
  </new-instance>
</cr:actions>

In response to this action, the process JPD and its support classes retrieve the symbol and investment manager for the current trade, find other unexecuted trades with the same symbol and investment manager and aggregate these trades into the same block.

After aggregating a trade, the rules engine is called again from a second Rules Executor control to evaluate rules to determine if the resulting block trade should be executed. According to business Rule 3, the rule is:

<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>

This time, we examine the Beans.Block object, getting the amount property to compare to the threshold. If the condition is met, we add a Beans.Action object to the working set with the command create, which is the signal to the process to execute the block order.

Let's examine the process JPD more closely. The code for the Control Send node that calls the rules engine is included below. As we can see, this node evaluates a rule set using a Rules Executor control, which returns an iterator. Via properties on the control (not shown), the control filters the results to only return objects of the class Beans.Action. From these, the code extracts the action command and executes the requested action. As stated earlier, if the action is to aggregate the trade, the process sets up a second call to the rules engine, using the updated block as input. There is a second loop to iterate over the results of that, with the appropriate actions executed.

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 assignments
    //#END  : CODE GENERATED - PROTECTED SECTION - you can safely 
      // Add code below this comment in this method. #//

    /* Iterate over the results of rules execution. This assumes that
       results are filtered to return only items of the Beans.Action class.  
       The command property from the Action is expected to be either the
       string "create," in which case a Block trade can be executed from
       the single discrete Trade, or it is expected to be a list of
       attributes describing the Block that this Trade should be 
       incorporated into.
     */
    while (results.hasNext())
    {
        String action = 
            ((Action)results.next()).getCommand();
        if (action.equals("create"))
            (new Block(trade)).execute();  // single-trade       
        else
        {
            // Aggregate trade into an intermediate Block
            trade.aggregate(blockStorage, action);
                
          /* Call the rules engine a second time, this time using
             the resulting Block as the only input.  This is to
             determine if the resulting Block now meets the criteria 
             to execute the order. Again, results are assumed to be
             filtered by the control to return only the Actions.
            */
            Block block = trade.getBlock();
            Object blockFacts[] = new Object[1];
            blockFacts[0] = block;
            Iterator blockResults = 
                 
                        
blockRulesCntl.evaluateRuleSet(blockFacts);
            while (blockResults.hasNext())
            {
              action = 
                ((Action)blockResults.next()).getCommand();
              if (action.equals("create"))
                  block.execute();
            }
        }
    }
}
                      

Dynamic Rules

One of the features of WebLogic Portal is the Datasync capability, which redeploys modified data to a portal application across a cluster. Because the portal rules engine gets its rules files from the Datasync store, the business rules for an application can be changed on a running system without application downtime. For additional information about Datasync and the Datasync Web application used to update data, refer to the WebLogic Portal documentation.

The Datasync Web application is deployed by default in a regular portal application; however, our sample application is a WLI process application, so we must manually include Datasync in our application. To do this from the Workshop IDE, right-click on the Modules folder and select Add Module. Then, select the file:

$BEA_HOME/weblogic81/p13n/lib/datasync.war

In a cluster, the Datasync application should only be deployed to the admin server.

To illustrate changing rules in a running application, the sample application includes a couple of different rules files containing alternative rules for aggregating trades into blocks. The default rules (defined in traderules.rls) are described above; a second set (in altrules.rls) defines the rules as:

  1. Aggregate all trades by symbol into blocks.
  2. Execute blocks containing 3,000 or more shares.

To see dynamic rules in action, first run the sample application with the supplied test data. Without stopping the server or redeploying the application, save the original rules in a new file, and copy the alternate rules file from:

/META-INF/data/rulesets/altrules.rls

into:

/META-INF/data/rulesets/traderules.rls

Rerun the sample data, and note that the resulting orders are different.

Note that in the sample domain, it was not necessary to run the Datasync Web application to update the rules. This is because the sample platform domain runs in development mode. In this mode, Datasync automatically polls the directory /META-INF/data and its subdirectories for changes; changed files are automatically redeployed to the application. In a production domain, it is necessary to use the Web application to drive the redeployment of data. The recommended procedure is to first create a jar file of all updated files; the root of the jar file should be the data directory. Then use the Bootstrap Data function of the Datasync Web application to redeploy the jar file containing the new rules.

Summary

The Portal Rules Engine is a useful tool for implementing business logic when used with a WLI process application. It is easy to incorporate into a WLI process, and you can use JavaBeans to evaluate rule conditions or to extend actions that are executed as the result of rules evaluation. Finally, the dynamic update capabilities provided by the Datasync feature make business logic flexible and adaptable to changing needs without Java code changes, so application redeployment is unnecessary.

Jitendra Gupta is a software engineer in the BEA Platform Engineering Team. At BEA, he has worked on WebLogic Platform Integration, end-to-end solution and customer use cases.

Mark Miller has over 20 years experience in the computer industry working on a diverse array of products including Web-based applications, Operating Systems, and Graphical Windowing Systems.

Venkatakrishnan Padmanabhan is a senior software engineer in the BEA Platform Engineering Team. Venkat has worked at BEA since 1996, starting as a consultant for Tuxedo QA and now focussing on architecting enterprise application using WebLogic Platform.