Drools JBoss Rules 5.0 Flow (Part 1)

0
102
10 min read

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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here