Drools JBoss Rules 5.0 Flow (Part 1)

Exclusive offer: get 50% off this eBook here
Drools JBoss Rules 5.0 Developer's Guide

Drools JBoss Rules 5.0 Developer's Guide — Save 50%

Develop rules-based business logic using the Drools platform

£18.99    £9.50
by Michal Bali | July 2009 | Java Open Source

Every non-trivial business process needs to make complex decisions. A rule engine is the ideal place for these decisions to happen. However, it is impractical to invoke a rule engine from a standard workflow engine. Instead, if we take a rule engine and add workflow capabilities, we have an ideal tool to model complex business processes—Drools Flow.

In this two-part article by Michal Bali, we'll look at Drools flow in detail. We'll build a loan approval process and cover the advanced concepts of a ruleflow like faults, subflows, and decisions.

Loan approval service

Loan approval is a complex process starting with customer requesting a loan. This request comes with information such as amount to be borrowed, duration of the loan, and destination account where the borrowed amount will be transferred. Only the existing customers can apply for a loan. The process starts with validating the request. Upon successful validation, a customer rating is calculated. Only customers with a certain rating are allowed to have loans. The loan is processed by a bank employee. As soon as an approved event is received from a supervisor, the loan is approved and money can be transferred to the destination account. An email is sent to inform the customer about the outcome.

Model

If we look at this process from the domain modeling perspective, in addition to the model that we already have, we'll need a Loan class. An instance of this class will be a part of the context of this process.

Drools JBoss Rules 5.0 Flow (Part 1)

The screenshot above shows Java Bean, Loan, for holding loan-related information. The Loan bean defines three properties. amount (which is of type BigDecimal), destinationAccount (which is of type Account; if the loan is approved, the amount will be transferred to this account), and durationYears (which represents a period for which the customer will be repaying this loan).

Loan approval ruleflow

We'll now represent this process as a ruleflow. It is shown in the following figure. Try to remember this figure because we'll be referring back to it throughout this article.

Drools JBoss Rules 5.0 Flow (Part 1)

The preceding figure shows the loan approval process—loanApproval.rf file. You can use the Ruleflow Editor that comes with the Drools Eclipse plugin to create this ruleflow. The rest of the article will be a walk through this ruleflow explaining each node in more detail.

The process starts with Validate Loan ruleflow group. Rules in this group will check the loan for missing required values and do other more complex validation. Each validation rule simply inserts Message into the knowledge session. The next node called Validated? is an XOR type split node. The ruleflow will continue through the no errors branch if there are no error or warning messages in the knowledge session—the split node constraint for this branch says:

not Message()

Code listing 1: Validated? split node no errors branch constraint (loanApproval.rf file).

For this to work, we need to import the Message type into the ruleflow. This can be done from the Constraint editor, just click on the Imports... button. The import statements are common for the whole ruleflow. Whenever we use a new type in the ruleflow (constraints, actions, and so on), it needs to be imported.

The otherwise branch is a "catch all" type branch (it is set to 'always true'). It has higher priority number, which means that it will be checked after the no errors branch.

The .rf files are pure XML files that conform with a well formed XSD schema. They can be edited with any XML editor.

Invalid loan application form

If the validation didn't pass, an email is sent to the customer and the loan approval process finishes as Not Valid. This can be seen in the otherwise branch. There are two nodes-Email and Not Valid. Email is a special ruleflow node called work item.

Email work item

Work item is a node that encapsulates some piece of work. This can be an interaction with another system or some logic that is easier to write using standard Java. Each work item represents a piece of logic that can be reused in many systems. We can also look at work items as a ruleflow alternative to DSLs.

By default, Drools Flow comes with various generic work items, for example, Email (for sending emails), Log (for logging messages), Finder (for finding files on a file system), Archive (for archiving files), and Exec (for executing programs/system commands).

In a real application, you'd probably want to use a different work item than a generic one for sending an email. For example, a custom work item that inserts a record into your loan repository.

Each work item can take multiple parameters. In case of email, these are: From, To, Subject, Text, and others. Values for these parameters can be specified at ruleflow creation time or at runtime. By double-clicking on the Email node in the ruleflow, Custom Work Editor is opened (see the following screenshot). Please note that not all work items have a custom editor.

Drools JBoss Rules 5.0 Flow (Part 1)

In the first tab (not visible), we can specify recipients and the source email address. In the second tab (visible), we can specify the email's subject and body. If you look closer at the body of the email, you'll notice two placeholders. They have the following syntax: #{placeholder}. A placeholder can contain any mvel code and has access to all of the ruleflow variables (we'll learn more about ruleflow variables later in this article). This allows us to customize the work item parameters based on runtime conditions. As can be seen from the screenshot above, we use two placeholders: customer.firstName and errorList. customer and errorList are ruleflow variables. The first one represents the current Customer object and the second one is ValidationReport. When the ruleflow execution reaches this email work item, these placeholders are evaluated and replaced with the actual values (by calling the toString method on the result).

Fault node

The second node in the otherwise branch in the loan approval process ruleflow is a fault node. Fault node is similar to an end node. It accepts one incoming connection and has no outgoing connections. When the execution reaches this node, a fault is thrown with the given name. We could, for example, register a fault handler that will generate a record in our reporting database. However, we won't register a fault handler, and in that case, it will simply indicate that this ruleflow finished with an error.

Test setup

We'll now write a test for the otherwise branch. First, let's set up the test environment.

Then a new session is created in the setup method along with some test data. A valid Customer with one Account is requesting a Loan. The setup method will create a valid loan configuration and the individual tests can then change this configuration in order to test various exceptional cases.

@Before
public void setUp() throws Exception {
session = knowledgeBase.newStatefulKnowledgeSession();

trackingProcessEventListener =
new TrackingProcessEventListener();
session.addEventListener(trackingProcessEventListener);
session.getWorkItemManager().registerWorkItemHandler(
"Email", new SystemOutWorkItemHandler());

loanSourceAccount = new Account();

customer = new Customer();
customer.setFirstName("Bob");
customer.setLastName("Green");
customer.setEmail("bob.green@mail.com");
Account account = new Account();
account.setNumber(123456789l);
customer.addAccount(account);
account.setOwner(customer);

loan = new Loan();
loan.setDestinationAccount(account);
loan.setAmount(BigDecimal.valueOf(4000.0));
loan.setDurationYears(2);

Code listing 2: Test setup method called before every test execution (DefaulLoanApprovalServiceTest.java file).

A tracking ruleflow event listener is created and added to the knowledge session. This event listener will record the execution path of a ruleflow—store all of the executed ruleflow nodes in a list. TrackingProcessEventListener overrides the beforeNodeTriggered method and gets the node to be executed by calling event.getNodeInstance().

loanSourceAccount represents the bank's account for sourcing loans.

The setup method also registers an Email work item handler. A work item handler is responsible for execution of the work item (in this case, connecting to the mail server and sending out emails). However, the SystemOutWorkItemHandler implementation that we've used is only a dummy implementation that writes some information to the console. It is useful for our testing purposes.

Testing the 'otherwise' branch of 'Validated?' node

We'll now test the otherwise branch, which sends an email informing the applicant about missing data and ends with a fault. Our test (the following code) will set up a loan request that will fail the validation. It will then verify that the fault node was executed and that the ruleflow process was aborted.

@Test
public void notValid() {
session.insert(new DefaultMessage());
startProcess();

assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_FAULT_NOT_VALID));
assertEquals(ProcessInstance.STATE_ABORTED,
processInstance.getState());
}

Code listing 3: Test method for testing Validated? node's otherwise branch (DefaultLoanApprovalServiceTest.java file).

By inserting a message into the session, we're simulating a validation error. The ruleflow should end up in the otherwise branch.

Next, the test above calls the startProcess method. It's implementation is as follows:

private void startProcess() {
Map<String, Object> parameterMap =
new HashMap<String, Object>();
parameterMap.put("loanSourceAccount", loanSourceAccount);
parameterMap.put("customer", customer);
parameterMap.put("loan", loan);
processInstance = session.startProcess(
PROCESS_LOAN_APPROVAL, parameterMap);
session.insert(processInstance);
session.fireAllRules();
}

Code listing 4: Utility method for starting the ruleflow (DefaultLoanApprovalServiceTest.java file).

The startProcess method starts the loan approval process. It also sets loanSourceAccount, loan, and customer as ruleflow variables. The resulting process instance is, in turn, inserted into the knowledge session. This will enable our rules to make more sophisticated decisions based on the state of the current process instance. Finally, all of the rules are fired.

We're already supplying three variables to the ruleflow; however, we haven't declared them yet. Let's fix this. Ruleflow variables can be added through Eclipse's Properties editor as can be seen in the following screenshot (just click on the ruleflow canvas, this should give the focus to the ruleflow itself). Each variable needs a name type and, optionally, a value.

Drools JBoss Rules 5.0 Flow (Part 1)

The preceding screenshot shows how to set the loan ruleflow variable. Its Type is set to Object and ClassName is set to the full type name droolsbook.bank.model.Loan. The other two variables are set in a similar manner.

Now back to the test from code listing 3. It verifies that the correct nodes were triggered and that the process ended in aborted state. The isNodeTriggered method takes the process ID, which is stored in a constant called PROCESS_LOAN_APPROVAL. The method also takes the node ID as second argument. This node ID can be found in the properties view after clicking on the fault node. The node ID—NODE_FAULT_NOT_VALID—is a constant of type long defined as a property of this test class.

static final long NODE_FAULT_NOT_VALID = 21;
static final long NODE_SPLIT_VALIDATED = 20;

Code listing 5: Constants that holds fault and Validated? node's IDs (DefaultLoanApprovalServiceTest.java file).

By using the node ID, we can change node's name and other properties without breaking this test (node ID is least likely to change). Also, if we're performing bigger re-factorings involving node ID changes, we have only one place to update—the test's constants.

Ruleflow unit testing
Drools Flow support for unit testing isn't the best. With every test, we have to run the full process from start to the end. We'll make it easier with some helper methods that will set up a state that will utilize different parts of the flow. For example, a loan with high amount to borrow or a customer with low rating.
Ideally we should be able to test each node in isolation. Simply start the ruleflow in a particular node. Just set the necessary parameters needed for a particular test and verify that the node executed as expected.
Drools support for snapshots may resolve some of these issues; however, we'd have to first create all snapshots that we need before executing the individual test methods. Another alternative is to dig deeper into Drools internal API, but this is not recommended. The internal API can change in the next release without any notice.

Drools JBoss Rules 5.0 Developer's Guide Develop rules-based business logic using the Drools platform
Published: July 2009
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

The size of the loan

All valid loans continue through the no errors branch to Amount to borrow? split node. It is again an XOR type split node. It works based on the amount property of Loan. If it is less than 5000, it continues through the low branch, otherwise, it takes the otherwise branch. The otherwise branch is again a 'catch all' type of branch. Put the following constraint into the split node:

Loan( amount <= 5000 )

Code listing 6: Amount to borrow? split node's low branch constraint (loanApproval.rf file).

For all loans that are bigger, a customer rating needs to be calculated.

Test for a small loan

The following method runs a loan with a small amount to borrow through our ruleflow. As can be seen in the following code, the first line of this test sets up a loan with low amount. Next, the process is started and the test verifies that the flow continued through the correct branch.

@Test
public void amountToBorrowLow() {
setUpLowAmount();
startProcess();

assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_JOIN_RATING));
assertFalse(trackingProcessEventListener
.isNodeTriggered(PROCESS_LOAN_APPROVAL,
NODE_SUBFLOW_RATING_CALCULATION));
}

Code listing 7: Test for the Amount to borrow? node's low branch (DefaultLoanApprovalServiceTest.java file).

The test expects the next XOR node on the low branch to be executed and it also expects that the next node on the otherwise branch—Rating Calculation—isn't executed.

The setupLowAmount method inserts a loan with low amount to borrow into the knowledge session. You could argue that loan could be a global variable instead of a fact. The advantage of having loan as a fact makes it possible to update it later on. Remember? Global variables shouldn't change when we want to reason over them.

Rating Calculation

The first node arising from the Amount to borrow? node's otherwise branch is a subflow node called Rating Calculation. This node will calculate the rating of this customer. It will then be used to decide if a loan should be granted or not.

Subflow

First, some general subflow information. Subflow is a normal ruleflow that can be called from another ruleflow. A subflow is effectively a ruleflow inside a ruleflow. The following are the benefits of doing this:

  • A complex flow can be logically separated into multiple simple flows. The problem can be decomposed into sub problems. As the basic principle says—divide and conquer.
  • The new subflow can be also reused in different contexts. For example, this rating calculation might be used in mortgage loan approval process. With the help of on-entry/on-exit actions and parameter mappings, the parent flow can supply information to the subflow and then possibly act on the result. The subflow remains independent.
  • This subflow can be executed in parallel with the parent flow. This means that after reaching the subflow node, the execution continues in both the parent flow and the subflow (note that this doesn't mean multiple threads). However, this has a disadvantage—we won't be able to use any results from this subflow in our parent flow.

The subflow is executed in the same knowledge session as the parent ruleflow. This means that the subflow can access facts just as its parent ruleflow. The StatefulKnowledgeSession.getProcessInstances() method can be used to return collection of all the process instances associated with a knowledge session.

Further, the subflow (and also some other ruleflow nodes) can define in/out parameter mappings and on-entry/on-exit actions. The parent flow will wait on a subflow if the Wait For Completion flag is set to true. Only in this case, it makes sense to use the out parameter mappings. Another flag that can be set is independent. With this flag set to true, the subflow will continue executing even if the parent ruleflow finished executing (it is completed or aborted); otherwise, it would be aborted.

Subflow diagram

The following subflow represents the rating calculation flow. After it starts, the execution continues through a split node. This split node is of type AND, meaning that the execution will continue in all of the node's outgoing branches. On the left side there is Calculate Incomes ruleflow group, and on the right side there are Calculate Monthly Repayments and Calculate Expenses ruleflow groups. These ruleflow groups contain rules for accumulating knowledge about customer incomes such as salaries of the customer and his/her spouse, type of occupation they have, for how long they have been employed, for how long they were unemployed, how much funds they have in their accounts, and information about their properties or other asserts. The Calculate Monthly Repayments ruleflow group calculates how much will this loan cost by month. The Calculate Expenses ruleflow group takes into account expenses such as the size of the family, rent, other loans, mortgages, and obligations.

Finally, these two branches are joined together by an AND type join node. This means that the flow won't continue until all of its incoming connections are triggered. The next node is a Calculate Rating ruleflow group. This is where all of the acquired information is translated by a set of rules into one number—rating. This ruleflow is as follows:

Drools JBoss Rules 5.0 Flow (Part 1)

Please note that we've named the split and join nodes as AND and AND. This may be a good practice to follow. The naming makes their type explicit. We no longer have to examine the node to see its type. The disadvantage is that you have to make sure that both the node's type and its name are updated at the same time. We've also used this naming convention in the parent ruleflow.

One important thing to remember when designing the ruleflow is to make it simple. The ruleflow should describe the core business process. It shouldn't contain every little detail of the process. The rules are ideal for this. They can then fine-tune the business process.

If we take the ruleflow from the preceding figure as an example, we can see that it logically separates the individual calculations in a very nice manner. By looking at this ruleflow diagram, you should immediately get a feeling of what it is trying to achieve.

    

Now, we know that the subflow uses rules to calculate a rating. This rating is a 'fact' inside the knowledge session. We also know that this rating will somehow be propagated to the parent flow through on exit action.

    

'On entry'/'On exit' Actions can be defined on various ruleflow nodes—subflow, work item, and human task. Ruleflow also supports Action as a standalone node. An action is simply a block of dialect-specific code. Action's code can access a context variable—org.drools.runtime.process.ProcessContext.

    

org.drools.runtime.process.ProcessContext
ProcessContext has various methods for working with the current ruleflow context. getProcessInstance() returns the current ruleflow instance. As our action is inside subflow, this method will return the subflow process instance.
In general, when a process starts, a new ProcessInstance is created that represents the runtime state of a process. Drools Flow is based on the PVM model (Process Virtual Machine—more information can be found at http://docs.jboss.com/jbpm/pvm/article/).
The getNodeInstance() method of ProcessContext returns the runtime instance of a currently executing node. The process context can also be used for setting and getting ruleflow variables getVariable / setVariable. The getKnowledgeRuntime() method returns KnowledgeRuntime that can be used for interaction with the knowledge session.

Nodes that define both entry/exit actions and also in/out parameter mappings use the following order to evaluate them:

  • On-entry actions
  • Input parameter mappings
  • The node itself
  • Output parameter mappings
  • On-exit actions

We'll define an on-exit action with the following body:

Rating rating = (Rating)context.getKnowledgeRuntime()
.getObjects(new ClassObjectFilter(Rating.class))
.iterator().next();
context.setVariable("customerLoanRating", rating.getRating());
update(context.getProcessInstance());

Code listing 8: Subflow node's onExit action body (loanApproval.rf file).

First of all, the action retrieves the calculated rating from the knowledge session. It simply iterates over all of the objects in the knowledge session and filters out all of the objects that are not of type Rating. Rating is a bean that has one property of type Integer called rating. The code is expecting to find just one Rating fact in the knowledge session as can be seen when we call the next method.

Next, we set the customerLoanRating variable using the context.setVariable method, which correctly sets it on the main ruleflow context. Finally, we shouldn't forget to update the processInstance because we've modified it.

Rating calculation subflow test

We'll now write a test which verifies that our subflow is being called and the variable is being set.

 @Test
public void amountToBorrowHighRatingCalculation() {
setUpHighAmount();
startProcess();
assertTrue(trackingProcessEventListener
.isNodeTriggered(PROCESS_LOAN_APPROVAL,
NODE_SUBFLOW_RATING_CALCULATION));
assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_RATING_CALCULATION,
NODE_GROUP_CALCULATE_RATING));
WorkflowProcessInstance process =
(WorkflowProcessInstance) processInstance;
assertEquals(1500,
process.getVariable("customerLoanRating"));
}

Code listing 9: Test for the subflow node (DefaultLoanApprovalServiceTest.java file).

The test sets up loan request with high amount by calling the setUpHighAmount method. This method inserts a loan (with amount set to 19000) into the knowledge session. Next, the process is started with default parameters, which involve customerLoanRating ruleflow variable set to zero. Next, the test verifies that the subflow node has been executed along with one node from the subflow—Calculate Rating. Finally, the test verifies that customerLoanRating variable has been set to 1500—it is a customer loan rating calculated for our test loan. The last couple of lines of the test method also show us how to get variables from the process instance.

The rules for calculating the rating have been left out. However, for testing purposes you could easily write a rule that inserts a Rating fact into the session with its rating property set to 1500

Another test for the rating calculation ruleflow may check that all of its nodes are executed, as the flow contains only and type split and join nodes.

Decisions on rating

After we've calculated rating and set it as ruleflow variable, the next ruleflow node—Rating?—checks if the customer's loan rating is high enough. It is an XOR type split node with the following accept branch constraint:

((Integer)customerLoanRating) >= 1000

Code listing 10: Rating? node's accept branch constraint—code type (loanApproval.rf file).

Set the type of this constraint to code and dialect to mvel. Code constraints have access to all of the ruleflow variables. As can be seen, we're directly referring to the customerLoanRating ruleflow variable and checking if it is greater or equal than 1000. If it is, the loan application can continue to the next step of loan approval process. Note that the variable needs to be cast to Integer; otherwise, an exception will be thrown.

If we need to take more complex decisions, we could use a rule type constraint:

processInstance : WorkflowProcessInstance(
eval( ((Integer)processInstance.getVariable(
"customerLoanRating")) >= 1000 ))

Code listing 11: Rating? node's accept branch constraint-rule type (loanApproval.rf file).

The condition uses a special variable name called processInstance of type WorkflowProcessInstance. It is special because Drools will match only on the current executing ruleflow instance even if there were multiple instances in the knowledge session. Through processInstance, we can access all of the ruleflow variables. Note that we need to insert the ruleflow instance into the knowledge session as we've done in code listing 4.

Testing the 'Rating?' node

The test will create a loan request for high amount and for a customer that has high rating. It will then execute the ruleflow and verify that the ruleflow execution went through the Rating? node through the accept branch to the XOR join node.

@Test
public void ratingSplitNodeAccept() {
setUpHighAmount();
setUpHighRating();
startProcess();

assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_SPLIT_RATING));
assertTrue(trackingProcessEventListener.isNodeTriggered(
PROCESS_LOAN_APPROVAL, NODE_JOIN_RATING));
}

Code listing 12: Rating? node's accept branch constraint (DefaultLoanApprovalServiceTest.java file).

The test executes successfully.

Summary

In this part, we've learned about various Drools Flow features. It represents an interesting approach to business process representation. The vision of Drools Flow is to unify rules and processes into one product. This is a very powerful idea, especially with business processes involving complex decisions because these complexities can be implemented within rules, which are ideal for this.

We've designed a loan approval service that involves validation of the loan request, customer rating calculation, and decisions on rating.

In the next part, we'll cover: Transfer Funds work Item, human tasks, and other aspects of ruleflow.

If you have read this article you may be interested to view :

Drools JBoss Rules 5.0 Developer's Guide Develop rules-based business logic using the Drools platform
Published: July 2009
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

About the Author :


Michal Bali

Michal Bali, freelance software developer, has more than 8 years of experience working with Drools and has an extensive knowledge of Java, JEE. He designed and implemented several systems for a major dental insurance company. He is an active member of the Drools community and can be contacted at michalbali@gmail.com.

Books From Packt

JBoss Drools Business Rules
JBoss Drools Business Rules

Flex 3 with Java
Flex 3 with Java

JBoss Tools 3 Developers Guide
JBoss Tools 3 Developers Guide

WordPress 2.7 Cookbook
WordPress 2.7 Cookbook

Spring Web Flow 2 Web Development
Spring Web Flow 2 Web Development

JBoss Portal Server Development
JBoss Portal Server Development

Apache Struts 2 Web Application Development
Apache Struts 2 Web Application Development

Alfresco 3 Enterprise Content Management Implementation
Alfresco 3 Enterprise Content Management Implementation

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software