Extending the power of the WebSphere Process Server business rules component

The WebSphere® Process Server business rules SCA component is used to express simple application logic that is too dynamic to implement in application source code. By using some clever techniques, business rule developers can provide users the capability to create and manage rules that work together to implement logic that goes beyond simple stand-alone declarations, such as "If the member has gold status, then increase award miles by 50%."

Share:

Francis DiNardo (fdinardo@us.ibm.com), Senior Managing Consultant, IBM

Francis Dinardo's pictureFrancis DiNardo is a Senior Managing Consultant with IBM Software Services for WebSphere and is based out of IBM Research Triangle Park near Raleigh, North Carolina. He specializes in software engineering and application development using IBM WebSphere middleware products.


developerWorks Contributing author
        level

06 May 2009

Introduction

The WebSphere Process Server business rules Service Component Architecture (SCA) component provides a mechanism for moving points of variability that are usually hardcoded into a format. This allows frequently changing logic to be easily maintained by business users, such as variables associated with promotional campaigns and customer policies.

Customizable business rule engines are becoming increasingly more important in service-oriented architectures (SOA) because they allow business users to manage the application changes associated with rapidly changing environments. Dynamic logic can be placed in small components and applied to the results of other modules. Consequently, new features are being added daily to the most recent releases of commercial business rule engines. Some of the newest engines support basic inference capabilities and allow for introspection of rules, adding a powerful dimension to the application of business rules.

Although the WebSphere Process Server business rules SCA component doesn't provide an inference engine, an SOA application developer can use some simple techniques to extend the native power of the business rules component. This article shows three simple techniques that can extend the power of your rule sets:

  • Rule grouping
  • Rule order
  • Rule contexts

We will use a fictitious law firm application as an example to demonstrate these techniques.

We'll examine these techniques in the context of an application that provides document access control for a large law firm that has many offices and handles a diversity of cases. A document associated with a legal action (a case) is submitted to the application (for the application of logic) to determine what kinds of employees can access the document. The application will examine the attributes of the document, as well as the attributes of the case, and return a string that contains a specification of what kind of employee types, roles, and levels can access the document. It is then up to the users of this service to determine, at the time of a request for access to the document, if the requester fits the constraints recommended by this service.

The sample application is not intended to behave like a true access control system, but it provides a good set of hooks on which to hang some of these concepts. The purpose of this article is not to provide an example of an access control system or how to manage a law firm; the point is to show how to combine rules to extend the power of the business rules component. Access control is a rich and convenient domain for showing how rule contexts can be used. In this case, we'll use business rules rather than typical access control system because the business rules allow you to respond quickly and easily to day-to-day changes in business policies, for example, it allows policies to be managed by business users rather than application developers.

The most interesting aspect of this application is that complex business policies regarding access to documents can be decomposed into sub-parts, with each sub-part represented by a rule. When a document's attributes are examined, multiple rules can fire, and the full set of rule firings can be analyzed and combined into a comprehensive recommendation. This not only supports the re-use of rules, it also allows you to manage complex policies in an organized fashion.

It's important to remember that when you allow business users to change the behavior of an application, the notion of validation and verification becomes a major issue. In addition, governance becomes important in order to maintain system integrity. These issues are beyond the scope of this article.


The sample application

The application was developed in WebSphere Integration Developer 6.1.2 and tested with the Universal Test Client using a WebSphere Process Server 6.1.2 (hereafter called Process Server) server provided with the development environment.

Figure 1 shows the organization of the application or service. The DetermineAccessProcess BPEL process drives the logic by invoking the DistributionCategoryRuleGroup partner, the CaseConstraintsRuleGroup partner, and the ConstraintAnalyzer partner. The services provided by each of these partners will be examined below.

Figure 1. The Assembly Diagram
The Assembly Diagram

The application uses a Service Component Architecture (SCA) binding to expose its interface to users of this service. The interface to the DetermineAccessProcess is the DetermineAccess_IF interface shown in Figure 2.

Figure 2. The DetermineAccess_IF interface
The DetermineAccess_IF interface

It contains a single 2-way operation that accepts a Case object as input and returns a string. The Case object contains the information associated with a particular legal matter, including a Document for which access permissions are being requested.

Figure 3. The Case and Document business objects
The Case and Document business objects

Attributes of both the Case object and the Document object are used to determine what kinds of employees can access the document.

Access constraints are specified by business users by means of business rules that have case and document properties on the IF side of the rules and values for EmployeeLevel, EmployeeType, or EmployeeRole on the THEN side of the rule. The interesting part of this application is that complex policies can be expressed by breaking down a policy into sub-parts and then creating multiple rules per policy. Multiple rules will fire and the results will be examined and combined into a single, complex recommendation.

The DetermineAccessProcess in Figure 4 shows the flow of the application logic.

Figure 4. The DetermineAccessProcess business process
The DetermineAccessProcess business process

This simple sequential flow does some document preparation before calling the 2 Rule Sets and a POJO that analyzes the results of the rule firings in the 2 Rule Sets. The "For" loop and the Java™ snippet it contains are there to convert the string array of document type names into a single string of comma separated by document type names. This conversion is done because some rule conditions will examine the document type names array looking for a specific value. Although rules work well with arrays and lists, you can't iterate through an array inside a single rule condition. You can accomplish the same thing by creating a loop outside the rule set that iterates through an array of values and passes each value to a single rule set in each iteration. However, rather than iterate through a single rule set, we chose to flatten the array and use the "contains" comparator in a rule condition to find a specific string.


Segmentation and rule chaining

In the early 1980's, rule based expert systems, also called production systems, became popular with the rise in interest in artificial intelligence. That popularity waned when neural network technology proved to be more effective in many pattern matching tasks and replaced rule based systems. Many of the rule based systems techniques have been reborn in some of the new business rules systems.

In 1979, Dr. Charles L. Forgy of Carnegie Mellon University introduced a pattern matching scheme called the Rete Algorithm, which allowed unordered rules to be compared to input data and "fired" in response to that data. A smart conflict resolution component determined which rules were eligible to fire, leaving expert system developers free from the constraint of having to order rules. That rule firing behavior provided a level of inferencing that was useful in pattern matching tasks. Rules fired, changing the values of input variables, and allowed other rules based on those new values to fire. This was known as "chaining", and had both forward and backward implementations.

Although the WebSphere Process Server business rules component does not implement the Rete Algorithm, a rule set developer can explicitly order rules to achieve a simple form of inference. This is a useful technique for decreasing the complexity of rules that have complex conditions.

For example, suppose you have a set of rules like this, where each upper case letter stands for a simple condition such as "temperature > 90":

R1: IF A and B and C and D THEN "It's summer"
R2: IF A and B and F THEN "It's summer"
R3: IF A and B and G and H THEN "It's winter"
R4: IF A and B and J and K THEN "It's autumn"

All of these rules contain the conditions A and B, which add to the clutter of the IF side of the rule. You can decrease the clutter and understandability of the rules by preceding those rules with a simpler rule and then use chaining to provide a simple inference, for example:

R0: IF A and B then X
R1: IF X and C and D THEN "It's summer"
R2: IF X and F THEN "It's summer"
R3: IF X and G and H THEN "It's winter"
R4: IF X and J and K THEN "It's autumn"

As long as R0 appears before the other rules (explicit ordering), you can infer X in the subsequent rules. This simplifies the rules and can make it easier to maintain rules.

The R0 rule doesn't even have to be in the same RuleSet as the rest of the rules, as long as R0 is in a RuleSet that executes before the RuleSet containing the other rules and the variable X is passed from one RuleSet to another.

Putting the predecessor rules in another RuleSet also implements another useful organizational technique - segmentation. Organizing rules into smaller sets make it easier to find and manage rules, especially if the RuleSets are used to group semantically similar rules.

This technique is demonstrated in the sample application provided with this article. You'll notice in Figure 1 and Figure 4 that two RuleGroups are invoked by the following business processes:

  • DistributionCategoryRuleGroup (containing the DistributionCategoryRuleSet)
  • CaseConstraintsRuleGroup (containing the CaseConstraintsRuleSet)

Many of the rules in the CaseConstraintsRuleSet refer to an attribute called DistributionCategory, which is derived from the value of other case or document attributes. Thus, it makes sense to put the rules that determine DistributionCategory in a separate RuleSet that is invoked before the CaseConstraintsRuleSet.

In the simple example shown in Figure 5, you see that DistributionCategory is determined by the values of the clientType and adversaryType variables that are associated with the case object. The rules in this RuleSet set the value of the DistributionCategory attribute of the outgoing case object so that it can be examined by rules in the subsequent RuleSet.

Figure 5. The DistributionCategory RuleSet
The DistributionCategory RuleSet

In the sample application, these rules are not dynamic - they will not be changed by business users. Keeping them in a separate RuleSet has value in this case because you are not cluttering up RuleSets that have changeable rules, making those dynamic rules easier to locate and manage. But if these rules were changeable, it still makes sense to separate them so that you can go directly to the sets of rules that are organized by similarities in content.


Capturing multiple rule firings

A typical rule contains an IF part that specifies rule conditions based on the value of variables and a THEN past that specifies actions, usually in the form of changes to the values of variables. Thus, a simple rule can exist and execute without regard to any other rules that might be part of the same or other RuleSets.

But real power is derived by setting up relationships between rules, as you saw in the previous section on chaining. Another technique, shown in Figure 6, is to capture the actions specified in a rule firing and holding on to it so that it can be examined in the context of other rule firings.

A special business object was created and used to capture the results of multiple rule firings. That object, the RuleFirings object, is shown in Figure 6.

Figure 6. RuleFirings business object
RuleFirings business object

The RuleFirings business object is a container for an array of single RuleFiring objects. Each RuleFiring object contains the results of a single rule firing. When a rule fires, it adds a new instance of a RuleFiring object, populates it with data, and then adds it to the array. It is this set of RuleFirings that is examined later by another component and converted into a meaningful document access specification.

When using dynamic business rules, a developer must develop rule templates from which rules can be created. Developers must understand the nature and structure of the actual rule instances to create templates, but developers will usually stop short of creating the actual rules. Creating and managing rules are the responsibility of business users, who must use the templates to manage rules.

Once the application is deployed, templates can be neither created nor modified without another deployment so rule templates must be created carefully. In some cases, this can limit the scope of the RuleSet.

For example, in the sample application, the rule conditions are built around four variables: distributionCategory, caseType, caseStatus, and docType. Not all the variables are necessarily a part of every rule, but any rule can have one or more of the variables as part of the conditions. Therefore, to accommodate all the combination of rule conditions that occur, there must be 15 rule templates: (2**4)-1 = 15.

You need 4 templates for the 4 rules that contain one of the variables, 6 templates for the set of rules that contain any two of the variables, 4 rules for the set of rules that contain any 3 of the variables, and 1 rule that contains all 4 variables. Keep in mind that the number of templates can increase rapidly as the number of variables increases, for example, 5 variables require (2**5)-1 or 31 templates, 6 variables require (2**6)-1 or 63 templates, and so on. This can get rapidly out of hand, so try to minimize the number of variables that use this approach.

We've shown how the number of templates can increase rapidly with 1 condition per variable, but it gets unworkable if you try to introduce OR conditions for multiple values for a single condition. But, since we're keeping track of multiple rule firings, this situation is avoidable by using a separate rule for each of the multiple values. For example:

IF docType = "draft" OR doctype = "final" THEN "xyz"

Can become:

IF docType = "draft" THEN "xyz"
IF docType = "final" THEN "xyz"

Both of the rules above will fire and, as you will see, our rule firing tracking scheme allows you to capture the logic correctly. Therefore, you don't have to create a large set of templates containing all the likely OR conditions (you can never capture all the possible combinations).

Let's take a look at the CaseConstraintRuleSet shown in Figure 7.

Figure 7. CaseConstraintsRuleSet interface and variables
CaseConstraintsRuleSet interface and variables

You can see from the interface that we're passing in a Case object containing case and document attributes used in rule conditions and returning a ruleFirings object that contains the results of all the rule firings. We also see the declarations for an integer index variable and a single ruleFiring object named newFiring. These are used in the action side of rules to add rule firings to the set of firings.

Before looking at the actual rules, let's first look at the templates (Figure 8).

Figure 8. CaseConstraintsRuleSet first single template (see enlarged Figure 8)
CaseConstraintsRuleSet first single template

This first template is used to create a rule that is concerned only with the distributionCategory attribute. This attribute is the one that contains a value set by the previous RuleSet - we discussed this in the section on rule chaining. This template contains parameters that allow you to create rules that deal with a single condition variable: distributionCategory, and three action variables: EmployeeType, EmployeeLevel, and EmployeeRole. Although there are three action variables, a rule derived from this template does not have to provide values for all of them. In fact, every template contains these three action variables and the assignment of values to any or all of those variables is determined by the values specified in the rule that is constructed from the template.

The "Then" part of the rule template is more interesting because it shows the technique for saving the results of each rule firing using a sequence of actions. The first action creates a new instance of a RuleFiring object. The next three actions update the attributes of the RuleFiring object with values that will be specified when a rule instantiating this template "fires". The next action adds the new RuleFiring object to the set of other rule firings and the last action increments the set index in preparation for the next rule firing.

This technique for capturing multiple rule firings is the foundation for understanding the context created by multiple rule firings. Although the interpretation of multiple rule firings can be application specific, you can use this technique for capturing multiple rule firings as the mechanism for aggregating the rules, regardless of the way in which the multiple rule firings will be interpreted. In this sample application, a Java component called the ContraintAnalyzer will be invoked after the RuleSet has executed. The ConstraintAnalyzer will examine the set of rule firings and "interpret" them in an application specific way.

Figure 9 shows additional single variable templates. They are similar to the previous template except they are concerned with the caseType and caseStatus variables, respectively.

Figure 9. CaseConstraintsRuleSet additional single templates (see enlarged Figure 9)
CaseConstraintsRuleSet additional single templates

Figure 10 shows two variable templates.

Figure 10. CaseConstraintsRuleSet 2 variables templates (see enlarged Figure 10)
CaseConstraintsRuleSet 2 variables templates

The first template is concerned with caseType and caseStatus and the next one is concerned with caseType and docType. These show that the variables are related in an AND condition.

Once all the templates have been created, rules can be created from those templates. Ordinarily, the actual rules are created by a business user, but we've created a set of rules that demonstrate how to combine rule firings in a meaningful way.

Let's look at those rules shown in Figure 11.

Figure 11. CaseConstraintsRuleSet rules (see enlarged Figure 11)
CaseConstraintsRuleSet rules

The first rule, named Init, initializes the RuleFirings array index to 0. As you saw in the templates, this index is used when a RuleFiring instance is added to the array of rule firings contained in the RuleFirings object.

Rule1 has been created from the template named TemplateDC, which has a single condition that queries the value of the document.distributionCategory attribute. In this case, if the value of the distributionCategory attribute is "Confidential", then set the required employeeLevel to "Junior". This represents a policy that says: IF the distributionCategory is "Confidential", THEN the employeeLevel must be "Junior".

You'll recall from the discussion about templates that we didn't create templates with more than one value for each action variable because the number of templates required to do this can easily get out of hand. Instead, create "OR" actions by having two instances of a rule that have the condition, but different values for a comparable action variable.

Rule2 is similar to Rule1 in that it contains the same condition, but sets a different value for the employeeLevel. This, in effect, allows you to specify this policy: IF the distributionCategory is "Confidential", THEN employeeLevel must be either "Junior" or "Senior".

Rule3 is concerned with the value of the caseType attribute of the Case object. If caseType is "Civil", then set the value of employeeType to "Attorney".

Rule4 is similar to Rule3 except that it's concerned with the value of the caseStatus attribute of the Case object and the employeeRole variable. If caseStatus is "Closed", then set the value of employeeRole to "Supervisor".

Any of these four rules can fire in isolation and contribute to the business policies, but when a case or document object is evaluated that contains values that cause the firing of all four rules, you can implement a complex policy that says this:

IF the distributionCategory is "Confidential" AND the caseType is "Civil" 
AND the caseStatus is "Closed" THEN only allow access to this document to 
employees that are an employeeLevel of "Junior" OR "Senior" AND that are an 
employeeType of "Attorney" AND are in an employeeRole of "Supervisor".

Thus, only a supervising Junior or Senior Attorney can access this document.

The next section describes how this logic is implemented.

Interpreting multiple rule firings

The real power of rules is utilized when they are combined in interesting ways to implement logic that is more than the firing of a single rule.

As mentioned earlier, the technique for capturing multiple rule firings is the foundation for understanding the context created by multiple rule firings. Although the interpretation of multiple rule firings can be application specific, this technique is used as the mechanism for aggregating the rules, regardless of the way in which the multiple rule firings will be interpreted.

We've worked on applications that assigned weights to rules, which were captured in rule firings and used to contribute to the meaning of a set of multiple rule firings. When unique weights are used, they can be used to determine which rule firings are more important than others. If non-unique rule weights are used, rules can be further grouped and classified.

Other applications assigned names to rules that were used to capture the history of rule firings or to contribute to application specific audit requirements.

In this sample application, a Java component called the ContraintAnalyzer will be invoked after the RuleSet has executed. The ConstraintAnalyzer examines the set of rule firings and "interprets" them in an application specific way. In our sample application, you want to examine the rule firings and combine the actions into a single string representing the access policy of the case or document object. This examination is not necessarily intended to replace other access control mechanisms, but it might participate in a more comprehensive scheme. Perhaps the results of this service can be passed to the access control system that implements the recommendations. In any case, our ContraintAnalyzer is intended to show you how to traverse the set of rule firings and draw some arbitrary, application specific conclusions from that set.

You'll recall that we created four rules that, when fired, set some values for three variables named employeeType, employeeLevel, and employeeRole. Those three variables were then assigned to corresponding attributes of the RuleFiring object. Each RuleFiring object was added to an array of RuleFiring objects contained in a container object named the RuleFirings object.

We ran a test using a case or document object containing attribute values that triggered all four rules. Consequently, we ended up with a RuleFirings object containing an array of four rule firing object instances that look like this:

Rule1 firing:

employeeType:
employeeLevel: Junior
employeeRole:

Rule2 firing:

employeeType:
employeeLevel: Senior
employeeRole:

Rule3 firing:

employeeType: Attorney
employeeLevel:
employeeRole:

Rule4 firing:

employeeType:
employeeLevel:
employeeRole: Supervisor

Our task is to examine the set of rule firings and combine the values into a string that is a logical expression of the access policies specified by the fired rules. The application specific logic (requirements) to apply to the set of rule firings is this:

  • When there are multiple values within an attribute, they are combined as an OR constraint.
  • When there are multiple values across attributes, they are combined as an AND constraint.

We've published a description of the simple syntax to express this logic so that clients of this service know how to read it.

In our rule firings, you see two values for the employeeLevel attribute and one value for each of the other two attributes. Therefore, we want to form a final string that looks like this:

(employeeType is Attorney) AND (employeeLevel is Junior OR Senior) AND 
 (employeeRole is Supervisor)

We won't examine the code line by line, but you can see that we retrieve the list of rule firings from the ruleFirings SDO DataObject with this line of code:

List ruleList = ruleFirings.getList("ruleFirings");

We then go through the list of rule firings and construct each sub-string of the full output string using the simple logic stated above. After examining each rule firing, we concatenate the sub-strings into a single output string.

The key is getting the list of rule firings from the DataObject. Once you have that list, you can iterate through it and interpret the set of firings any way described in the requirements of the application.

The code:

public String getPermissions(DataObject ruleFirings) {
   StringBuffer permissionString = new StringBuffer();
   StringBuffer employeeTypes = new StringBuffer();
   StringBuffer employeeLevels = new StringBuffer();
   StringBuffer employeeRoles = new StringBuffer();
   boolean employeeTypeExists = false;
   boolean employeeLevelExists = false; 
   boolean employeeRoleExists = false; 
   
   // initialize permission strings - don't care if they receive values 
   employeeTypes.append(" ( ");
   employeeLevels.append(" ( ");
   employeeRoles.append(" ( ");		
   
   List ruleList = ruleFirings.getList("ruleFirings");
   Iterator ruleIter = ruleList.iterator();
   while (ruleIter.hasNext()){
      DataObject currentRuleFiring = (DataObject)ruleIter.next();			
      if (currentRuleFiring.getString("employeeType").length() > 0) {
         if (employeeTypeExists) {
            employeeTypes.append(" OR ");
         } else {
            employeeTypes.append(" employeeType is ");
            employeeTypeExists = true;
         }
         employeeTypes.append(currentRuleFiring.getString
          ("employeeType"));							
      } // end if			
      if (currentRuleFiring.getString("employeeLevel").length() > 0) {
         if (employeeLevelExists) {
            employeeLevels.append(" OR ");
         } else { 
            employeeLevels.append(" employeeLevel is ");
            employeeLevelExists = true;
         }
         employeeLevels.append(currentRuleFiring.getString
          ("employeeLevel"));							
      } // end if			
      if (currentRuleFiring.getString("employeeRole").length() > 0) {
         if (employeeRoleExists) {
            employeeRoles.append(" OR ");
         } else { 
            employeeRoles.append(" employeeRole is ");
            employeeRoleExists = true;
         }
         employeeRoles.append(currentRuleFiring.getString
          ("employeeRole"));							
      } // end if			
   } // end while
   
   // put closing parenthesis on any employee attribute sets, even empty ones
   employeeTypes.append(" ) ");
   employeeLevels.append(" ) ");
   employeeRoles.append(" ) ");
   
   // append each employee attribute set to the main output string
   permissionString.append(employeeTypes);
   permissionString.append(" AND ");
   permissionString.append(employeeLevels);
   permissionString.append(" AND ");
   permissionString.append(employeeRoles);
   
   // printout the string
   System.out.println(permissionString.toString());
   
   // return the constructed permisssions string
   return permissionString.toString();
}

Conclusion

This article discussed techniques for ordering, grouping, and contextualizing rules in ways that add additional meaning to the results of rule firings. Although the WebSphere Process Server SCA business rules component does not contain capabilities for performing advanced logic, the techniques described can extend the power of your business rules.


Download

DescriptionNameSize
Code sampleAdvancedRulesMod_PI_sample.zip25 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=386289
ArticleTitle=Extending the power of the WebSphere Process Server business rules component
publish-date=05062009