The advantage of separating out decision points as external rules is that we not only ensure that each rule is used in a consistent fashion, but in addition make it simpler and quicker to modify; that is we only have to modify a rule once and can do this with almost immediate effect, thus increasing the agility of our solution.
Business Rule concepts
Before we implement our first rule, let's briefly introduce the key components which make up a Business Rule. These are:
- Facts: Represent the data or business objects that rules are applied to.
- Rules: A rule consists of two parts, an IF part which consists of one or more tests to be applied to fact(s), and a THEN part, which lists the actions to be carried out should the test to evaluate to true
- Rule Set: As the name implies, it is just a set of one or more related rules that are designed to work together .
- Dictionary: A dictionary is the container of all components that make up a business rule, it holds all the facts, rule sets, and rules for a business rule.
In addition, a dictionary may also contain functions, variables, and constraints. We will introduce these in more detail later in this article.
To execute a business rule, you submit one or more facts to the rules engine. It will apply the rules to the facts, that is each fact will be tested against the IF part of the rule and if it evaluates to true, then it will perform the specified actions for that fact. This may result in the creation of new facts or the modification of existing facts (which may result in further rule evaluation).
Leave approval rule
To begin with, we will write a simple rule to automatically approve a leave request that is of type Vacation and only for 1 day's duration. A pretty trivial example, but once we've done this we will look at how to extend this rule to handle more complex examples.
Using the Rule Author
In SOA Suite 10.1.3 you use the Rule Author, which is a browser based interface for defining your business rules. To launch the Rule Author within your browser go to the following URL:
http://<host name>:<port number>/ruleauthor/
This will bring up the Rule Author Log In screen. Here you need to log in as user that belongs to the rule-administrators role. You can either log in as the user oc4jadmin (default password Welcome1), which automatically belongs to this group, or define your own user.
Creating a Rule Repository
Within Oracle Business Rules, all of our definitions (that is facts, constraints, variables, and functions) and rule sets are defined within a dictionary. A dictionary is held within a Repository.
A repository can contain multiple dictionaries and can also contain multiple versions of a dictionary. So, before we can write any rules, we need to either connect to an existing repository, or create a new one.
Oracle Business Rules supports two types of repositoryâ€”File based and WebDAV. For simplicity we will use a File based repository, though typically in production you want to use a WebDAV based repository as this makes it simpler to share rules between multiple BPEL Processes.
WebDAV is short for Web-based Distributed Authoring and Versioning. It is an extension to HTTP that allows users to collaboratively edit and manage files (that is business rules in our case) over the Web.
To create a File based repository click on the Repository tab within the Rule Author, this will display the Repository Connect screen as shown in the following screenshot:
From here we can either connect to an existing repository (WebDAV or File based) or create and connect to a new file-based repository. For our purposes, select a Repository Type of File, and specify the full path name of where you want to create the repository and then click Create.
To use a WebDAV repository, you will first need to create this externally from the Rule Author. Details on how to do this can be found in Appendix B of the Oracle Business Rules User Guide (http://download.oracle.com/docs/cd/B25221_04/web.1013/b15986/toc.htm). From a development perspective it can often be more convenient to develop your initial business rules in a file repository. Once complete, you can then export the rules from the file repository and import them into a WebDAV repository.
Creating a dictionary
Once we have connected to a repository, the next step is to create a dictionary. Click on the Create tab, circled in the following screenshot, and this will bring up the Create Dictionary screen. Enter a New Dictionary Name (for example LeaveApproval) and click Create.
This will create and load the dictionary so it's ready to use. Once you have created a dictionary, then next time you connect to the repository you will select the Load tab (next to the Create tab) to load it.
Before we can define any rules, we first need to define the facts that the rules will be applied to. Click on the Definitions tab, this will bring up the page which summarizes all the facts defined within the current dictionary.
You will see from this that the rule engine supports three types of facts: Java Facts, XML Facts, and RL Facts. The type of fact that you want to use really depends on the context in which you will be using the rules engine.
For example, if you are calling the rule engine from Java, then you would work with Java Facts as this provides a more integrated way of combining the two components. As we are using the rule engine with BPEL then it makes sense to use XML Facts.
Creating XML Facts
The Rule Author uses XML Schemas to generate JAXB 1.0 classes, which are then imported to generate the corresponding XML Facts. For our example we will use the Leave Request schema, shown as follows for convenience:
<?xml version="1.0" encoding="windows-1252"?>
<xsd:element name="leaveRequest" type="tLeaveRequest"/>
<xsd:element name="employeeId" type="xsd:string"/>
<xsd:element name="fullName" type="xsd:string" />
<xsd:element name="startDate" type="xsd:date" />
<xsd:element name="endDate" type="xsd:date" />
<xsd:element name="leaveType" type="xsd:string" />
<xsd:element name="leaveReason" type="xsd:string"/>
<xsd:element name="requestStatus" type="xsd:string"/>
Using JAXB, particularly when used in conjunction with BPEL, places a number of constraints on how we define our XML Schemas, including:
- When defining rules, the Rule Author can only work with globally defined types. This is because it's unable to introspect the properties (i.e. attributes and elements) of global elements.
- Within BPEL you can only define variables based on globally defined elements.
The net result is that any facts we want to pass from BPEL to the rules engine (or vice versa) must be defined as global elements for BPEL and have a corresponding global type definition so that we can define rules against it.
The simplest way to achieve this is to define a global type (for example tLeaveRequest in the above schema) and then define a corresponding global element based on that type (for example, leaveRequest in the above schema).
Even though it is perfectly acceptable with XML Schemas to use the same name for both elements and types, it presents problems for JAXB, hence the approach taken above where we have prefixed every type definition with t as in tLeaveRequest.
Fortunately this approach corresponds to best practice for XML Schema design.
The final point you need to be aware of is that when creating XML facts the JAXB processor maps the type xsd:decimal to java.lang.BigDecimal and xsd:integer to java.lang.BigInteger. This means you can't use the standard operators (for example >, >=, <=, and <) within your rules to compare properties of these types. To simplify your rules, within your XML Schemas use xsd:double in place of xsd:decimal and xsd:int in place of xsd:integer.
To generate XML facts, from the XML Fact Summary screen (shown previously), click Create, this will display the XML Schema Selector page as shown:
Here we need to specify the location of the XML Schema, this can either be an absolute path to an xsd file containing the schema or can be a URL.
Next we need to specify a temporary JAXB Class Directory in which the generated JAXB classes are to be created.
Finally, for the Target Package Name we can optionally specify a unique name that will be used as the Java package name for the generated classes. If we leave this blank, the package name will be automatically generated based on the target namespace of the XML Schema using the JAXB XML-to-Java mapping rules. For example, our leave request schema has a target namespace of http://schemas.packtpub.com/LeaveRequest; this will result in a package name of com.packtpub.schemas.leaverequest.
Next click on Add Schema; this will cause the Rule Author to generate the JAXB classes for our schema in the specified directory. This will update the XML Fact Summary screen to show details of the generated classes; expand the class navigation tree until you can see the list of all the generated classes, as shown in the following screenshot:
Select the top level node (that is com) to specify that we want to import all the generated classes. We need to import the TLeaveRequest class as this is the one we will use to implement rules and the LeaveRequest class as we need this to pass this in as a fact from BPEL to the rules engine.
The ObjectFactory class is optional, but we will need this if we need to generate new LeaveRequest facts within our rule sets. Although we don't need to do this at the moment it makes sense to import it now in case we do need it in the future.
Once we have selected the classes to be imported, click Import (circled in previous screenshot) to load them into the dictionary. The Rule Author will display a message to confirm that the classes have been successfully imported. If you check the list of generated JAXB classes, you will see that the imported classes are shown in bold.
In the process of importing your facts, the Rule Author will assign default aliases to each fact and a default alias to all properties that make up a fact, where a property corresponds to either an element or an attribute in the XML Schema.
Oracle Business Rules allows you to specify your own aliases for facts and properties in order to define more business friendly names which can then be used when writing rules.
For XML facts if you have followed standard naming conventions when defining your XML Schemas, we typically find that the default aliases are clear enough and that if you start defining aliases it can actually cause more confusion unless applied consistently across all facts.
Hiding facts and properties
The Rule Author lets you hide facts and properties so that they don't appear in the drop downs within the Rule Author. For facts which have a large number of properties, hiding some of these can be worth while as it can simplify the creation of rules.
Another obvious use of this might be to hide all the facts based on elements, since we won't be implementing any rules directly against these. However, any facts you hide will also be hidden from BPEL, so you won't be able to pass facts of these types from BPEL to the rules engine (or vice versa).
In reality, the only fact you will typically want to hide will be the ObjectFactory (as you will have one of these per XML Schema that you import).
Saving the rule dictionary
As you define your business rules, it makes sense to save your work at regular intervals. To save the dictionary, click on the Save Dictionary link in the top right hand corner of the Rule Author page.
This will bring up the Save Dictionary page. Here either click on the Save button to update the current version of the dictionary with your changes or, if you want to save the dictionary as a new version or under a new dictionary name, then click on the Save As link and amend the dictionary name and version as appropriate.
Creating a rule set
Once we have defined our facts, we are ready to implement our first rule set. Click on the Rulesets tab within the Rule Author, which will bring up the RuleSet Summary page. This will initially be empty, as shown in the following screenshot:
Click on Create and this will bring up the Ruleset page, as shown in the following screenshot:
Enter a name, for example LeaveApprovalRules, and an optional description and then click Apply.
This will update the RuleSet Summary page (shown in the following screenshot), showing you details of the newly created rule set, plus a list of its rules which is currently empty.
Adding a rule to our rule set
To create a rule, click on the Create button (circled in the previous screenshot); this will bring up the Rule page where we can give the rule a meaningful name (for example OneDayVacation) and optionally provide a Description for the rule as well.
From this we can see a rule consists of two parts, an If part which consists of one or more tests (or patterns) to be applied to a fact or facts, and a Then part, which specifies the actions to be carried out should the test to evaluate to true.
Creating the If clause
To create the If clause we need to define one or more patterns to be applied. To define a pattern click on New Pattern, this will open the Pattern Definition window, as shown in the following screenshot:
A Pattern consists of two parts, the first is the type of pattern that we wish to test for, and the second is the tests we want to apply to the pattern.
Choosing the pattern
The first drop down is used to specify the type of pattern that we want to test for; this can take one of the following three values:
- Blank: This is the default pattern, and is used to specify that the rule should be applied to each fact, where the test evaluates to true. So, for example, if we submitted multiple leave requests in one go, we would the trigger the rule for each leave request that is of type Vacation and only 1 day in duration.
- There is at least one case: With this option, the rules will only be triggered once, as long as there is at least one match.
- There is no case: With this option, the rule will be fired once if there are no matches.
With the second drop down we specify the type of fact that we wish to apply the rule to. In our case we want to test facts of type TLeaveRequest. The text area before this is used to assign a temporary alias to the fact being tested, i.e. request in our case. This alias is useful when testing multiple facts of the same type.
Defining the test for the pattern
For our leave approval rule we need to define two tests, one to check that the request is only for 1 day in duration, which we can do by checking that the start date equals the end date, the second to check that the request is of type Vacation.
To define the first test, click the Create button; this will add a row to our Define Test for Pattern table where we can define the test conditions (as shown in the following screenshot).
In the first Operand drop down, select the value to be tested, for example request.StartDate in our case. Next from the Operator drop down select the test to be applied to the first operand (== in our case). Next we can either choose to compare it to a specified value or a second Operand. For our purpose we want to check that the request.startDate equals the request.endDate.
To create our second test, we follow pretty much the same process. This time we want to test the operand leaveRequest.leaveType is equal to the value Vacation.
You may have noticed when specifying the second operand for each test that there is another drop down containing the values Fixed (as selected in the previous screenshot) or Any. Oracle Rules refers to these values as constraints. If an operand is set to Any, this specifies that non-technical users of the Rule Author can use the customization tab to modify the value of the operand.
Once we've defined both our tests, then click OK; this will take us back to the Rule page, which will now be updated with details of the If clause, as shown in the following screenshot:
Creating the Then clause
Now that we have defined our test, we need to define the action to take if the test evaluates to true. Click on New Action. This will pop up the Add Action window where you need to specify the Action Type you wish to carry out.
The Rule Engine supports the action types listed below:
- Assert: Used to reassert any facts matched in a pattern (for example, request). When a fact is altered, if we want the rule engine to be aware of the change and re-evaluate the modified fact against the rule set, we must assert it.
- Assert New: If we create a new fact, for example a new LeaveRequest, then we must assert the new fact to make the rule engine aware of it, so that it can evaluate the new fact against the rule set.
- Assign: We can use this to either assign a value to a variable or a fact property; in our case we want to assign a status of Approved to the request.requestStatus property.
- Call: This allows you to call a function to perform one or more actions.
- Retract: This enables you to retract any of the facts matched in the pattern (for example request) so that it will no longer be evaluated as part of the rule set.
- RL: Allows you to enter RL text directly to perform one or more actions.
The actions Assert, Assert New, and Retract, are important when we are dealing with rule sets which deal with multiple interdependent facts, as this allows us to control which facts are being evaluated by the rule engine at any one time. Here, we only are dealing with a single fact, so don't examine these constructs in this article.
For our purpose we want to update the status of leave request to approved, so select Assign as the Action Type. This will update the Add Action screen shown as follows:
From the drop down select request.requestStatus as the variable that we wish to assign a value to. Then in the Expression field enter the value of Approved.
To calculate the value based on a more complicated formula, we can use the Expression Wizard to build this; the wizard is launched by clicking on the pencil icon.
Once we have completed our assign action, then click OK, this will take us back to the Rule page, which will now be updated with details of the Then clause, as shown in the following screenshot:
At this point make sure you save the dictionary. This completes the definition of our LeaveApproval rule set. The next step is to wire it into our BPEL process.
Business rules are a key component of any application. Traditionally these rules are buried deep within the code of an application, making them very difficult to change. Yet, in a typical application it is the business rules which change most frequently; by separating these out as a specialized service it allows us to change these rules without having to modify the overall application.
In this article we have looked at how we can use the Oracle Business Rules engine to implement such rules, and how we can invoke these from within BPEL as a decision service. In the next part of this article, we will have a look at how to create a Decision Service.