This blog ticket shows how to use Drools, one of the most popular Business Rules Engine, with WebLogic application servers.
JBoss Drools (https://www.drools.org/) is a BRE (Business Rules Engine) which aims at facilitating the implementation and the integration of the business rules into Java code. It has a community release provided by JBoss.org, as well as a commercial one, provided by Red Hat and known as JBoss BRMS (Business Rules Management System). In fact, JBoss BRMS is a full-fledged platform including an EAP (Enterprise Application Platform) server together with Drools, the BPM (Business Process Management) engine and other business intelligence components. But in this article we’ll be using the community release of JBoss Drools.
BREs are software systems that allow to define and execute business rules in a production system. A business rule is a statement that describes a business procedure or policy. It typically supports rules, facts, priority (score), mutual exclusion, preconditions, and other functions.
In the past, business rules used to be implemented directly in the code, using the programming language’s constructs like alternative and repetitive structures. The modern art of programming requires to isolate and externalize business rules from the code, achieving this way an increased flexibility of the services and applications. One of the most important advantages of using BREs is the fact of avoiding to have to change the services and applications code each time that business is changing. The other big advantage is that they allow non-programmers, like business analysts, to define and maintain business rules as business is changing. This wouldn’t have been possible, of course, as long as the business rules were implemented in the code because, in this case, defining, modifying and maintaining the rules require the understanding of the programming language, and this is something which cannot be expected from business analysts.
A very common example of a business rule, the one which could be found in most of tutorials, shows how to apply different discount rates depending on the customer profile, i.e. on the customer type (private or business) and the customer fidelity, expressed as the number of years of customership. A Java code that implements a business rule might look like the following fragment:
package de.telefonica.rules_management.facade;
import de.telefonica.rules_management.facts.*;
import de.telefonica.rules_management.facts.Customer.*;
public class Rules
{
public void applyDiscount (Customer customer)
{
if (customer.getType().equals(CustomerType.INDIV IDUAL))
{
if (customer.getYears() >= 3)
customer.setDiscount(15);
else if (customer.getYears() >=0 && customer.getYears() < 3)
customer.setDiscount(3);
}
else if (customer.getType().equals(CustomerType.BUSINESS))
customer.setDiscount(20);
// ...
}
}
The code fragment above implements a sample business rules which applies a discount rate of 3%, 15% or 20% on the sold products. It clearly states that all business customers must have the right to receive a discount rate of 20% on the ordered goods, while the private customers will beneficiate of 3% or 15% depending on weather they have less than 3 years of customership. This is a very simple business rule but, in a typical use case, they might become very complicated, with a lot of different imbricated if then else constructs, making their maintenance very complicated and expansive.
A BRE proposes a high level definition language for business rules. In the case of Drools the following alternatives exist:
- Creating DRL (Drools Rules Language) files from an UI application called Drools Dashboard, in the community edition, and Business Central in BRMS.
- Using a Graphical Guided Rule Editor from BRMS.
- Authoring rules from JBoss Developer Studio and Drools IDE plugins.
- Creating rules using a custom DSL (Domain Specific Language).
- Creating rules using Microsoft Excel decision tables.
From all these alternatives above, the most interesting and accessible to business analysts is probably the last one. This is also the method that we choose to use in our test case. In order to access the samples, you first need to clone the git repository, as follows:
mkdir rules
cd rules
git clone https://github.com/nicolasduminil/rules.git
Now you may look at the Discount.xls file in the src/main/resources/de/Telefonica/drools/rules directory.
RuleSet |
de.telefonica.drools |
||||
Import |
de.telefonica.rules_management.facts.Customer, de.telefonica.rules_management.facts.Customer.CustomerType |
||||
|
|
|
|
|
|
RuleTable Discount rates |
|||||
NAME |
CONDITION |
CONDITION |
CONDITION |
ACTION |
|
|
$customer:Customer |
|
|||
|
$customer.getType() in ($param) |
$customer.getYears() >= ($param) |
$customer.getYears() < ($param) |
$customer.setDiscount($param); |
|
NAME |
Customer type |
Years |
Years |
Discount |
|
Individual > 3y |
CustomerType.INDIVIDUAL |
3 |
|
15 |
|
Individual < 3y |
CustomerType.INDIVIDUAL |
0 |
3 |
5 |
|
Business Any |
CustomerType.BUSINESS |
|
|
20 |
Each column and line in the Excel sheet above has a special meaning, as follows:
- Row 1: Contains a RuleSet attribute which identifies the Excel sheet as decision table containing Drools rules. The name of the rule set is de.telefonica.drools.
- Row 2: Uses the Import keyword to specify the facts used by the rules. These facts are POJO (Plain Old Java Objects) that the rules are handling. In our case, we deal here with Customer and CustomerType Java classe and enum. Have a look at these Java class/enum to see the properties they define.
- Row 4: The RuleTable attribute indicates the beginning of a rule table. Each spreadsheet may contain several rule tables and each rule table has a name which, in our case is Discount rates. This name is used as a prefix to the name of each rule defined in the table rules.
- Row 5: Here we define the columns role. These roles are:
- Name: this is the column containing the rules names
- Condition: contains the right part of a DRL rule. If several rules are defined, then they are implicitly in conjunction (AND) each-other.
- Action: contains the left part of a DRL rule. This is typically the reference of the Java method to be called when all the conditions are satisfied.
- Row 6: Defines a variable named $customer of the type Customer. This variable will be used farther.
- Row 7: Defines the conditions and the action to be performed when the conditions are satisfied. Here $param refers to the dynamical values defined in the cells below (rows 8, 8 and 10). For example, $param value column 2 is INDIVIDUAL (an enum value), while in column 5 is 15.
- Row 8: Contains column description.
- Rows 9 to 11: Defines the set of rules to be checked based on the conditions at the Row 7. $param will be replaced by the value in the same column, for example by 3 in the case of the rule named “Individual>3y” and by 0 in the case of the rule named “Individual<3y”. Hence, the rule “Individual>3y” checks, in the Row 7 Column 2, whether the customer type is INDIVIDUAL and the number of customership years is greater or equal to 3, etc. Then the action defined in the Row 7, Column 5 initializes the discount property of the customer object with the $param value, which is the one of the same column in the lines below, i.e.:
- 15 if the customer is individual and has more than 3 years of customership.
- 5 if the customer is individual and has less than 3 years of customership.
- 20 if the customer is business.
In addition of directly using Excel spreadsheets, JBoss BRMS provides a set of options to define such decision tables and then produce Drools rules out of them. Typically, this is done by using the graphical user interface named Business Central, which is able to inspect facts present in the working memory and their properties. This might have lead many developers to think that using Drools and decision tables based rules would require using JBoss BRMS or at least JBoss application server. In this tutorial we choose to use Oracle WebLogic application server. This decision was made on the purpose to show that ther is no any obligation that Drools be used with JBoss technology.
The access to the Drools working memory, which is the place where rules are defined and validated, is done via something named KIE (Knowledge Is Everything). This is kind of a knowledge base containing the facts and the rules known at a given time. This KIE is, of course, implemented by Drools specific classes in the org.kie.api package. In the past it was quite difficult to get the list of all the dependencies required in order to handle the KIE hence, using JBoss BRMS, was very convenient as this platform comes out-of-the-box with all these artifacts. However, this problem doesn’t exist anymore nowadays and, in order to use Drools and KIE with any any non JBoss technology, one only needs the following dependencies:
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>7.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-ci</artifactId>
<version>7.5.0.Final</version>
</dependency>
Here we are using Drools 7.5 which is the last release as per this writing.
Our implementation is based on a stales session bean, named KieFacade, having a local and a remote interface, named KieFacadeLocal and, respectively, KieFacadeRemote. These interfaces are very simple, having only one entry-point called fireRules(). This method, as its name implies, fire all the rules defined in the working memory for a given customer. As for the implementation, here is the code:
package de.telefonica.rules_management.facade;
import javax.annotation.*;
import javax.ejb.*;
import org.kie.api.*;
import org.kie.api.builder.*;
import org.kie.api.runtime.*;
import org.kie.api.runtime.rule.*;
import org.kie.internal.io.*;
import de.telefonica.rules_management.facts.*;
@Stateless
public class KieFacade implements KieFacadeLocal, KieFacadeRemote
{
private KieSession kieSession;
@PostConstruct
public void init()
{
KieServices kieServices = KieServices.Factory.get();
KieBuilder kieBuilder =
KieServices.newKieBuilder(kieServices.newKieFileSystem()
.write(ResourceFactory.newClassPathResource("de/telefonica/drools/rules/Discount.xls",getClass())));
kieBuilder.buildAll();
kieSession =
kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId()).newKieSession();
}
public int fireRules(Customer customer)
{
FactHandle fact = kieSession.insert(customer);
kieSession.fireAllRules();
kieSession.delete(fact);
return customer.getDiscount();
}
@PreDestroy
public void destroy()
{
kieSession.destroy();
kieSession.dispose();
}
}
The code above shows the stateless session bean, named KieFacade, which implements both KieFacadeLocal and KieFacadeRemote interfaces. In its @PostConstruct methos it instantiates the KIE on the behalf of the KieBuilder factory. The Discount.xls file which contains the Excel decision table id given as an input parameter to the factory and this has the effect of populating the KIE working memory with the rules defined there. As for the facts, they are defined by the de.telefonica.rules_management.facts package. Once instantiated, the KIE stays active until the session bean is stopped, or undeployed, or the application server is stopped. Then the @PreDestroy method is executed to destroy the KIE and release the associated working memory.
The method fireRules() takes a customer as an input parameter. It inserts this object in the working memory as a new fact and applies on it all the defined rules. It then deletes the new added fact from the working memory and returns the discount property calculated based on the application of the rules.
In order to test the whole stuff, the following unit test is proposed:
package de.telefonica.rules_management;
import java.util.*;
import javax.naming.*;
import org.junit.*;
import de.telefonica.rules_management.facade.*;
import de.telefonica.rules_management.facts.*;
import de.telefonica.rules_management.facts.Customer.*;
public class TestFacade
{
@Test
public void test() throws Exception
{
Hashtable<String, String> props = new Hashtable<String, String>();
props.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
props.put(Context.PROVIDER_URL, "t3://localhost:7001");
KieFacadeRemote facade = (KieFacadeRemote) new InitialContext(props)
.lookup("java:global.rules-manager.KieFacade!de.telefonica.rules_management.facade.KieFacadeRemote");
Assert.assertEquals(15, facade.fireRules(new Customer(CustomerType.INDIVIDUAL, 5)));
}
}
This unit test, which in fact is an integration test, looks up the remote interface reference in the WebLogic application server JNDI namespace. The properties passed in the input hash table parameter are WebLogic specific. The client is a standalone one, running out of the container, hence it has to lookup the remote interface. A servlet or another component running in the container would have used the local interface. Once the EJB reference is obtained from the JNDI namespace, a new individual customer, with 5 years of customership, is instantiated. This new customer will be passed in to the fireRules() method of the EJB session bean. Based on the fact that the customer is an individual one and has 5 years of customership, the calculated discount rate resulting from the application of the defined rules is 15%.
Now to build and to run the sample, one needs to perform the following operations:
cd rules #move to the home directory
mvn –DskipTests clean install #build the WAR file while skipping the test phase
mvn com.oracle.weblogic:weblogic-maven-plugin:deploy #deploy the WAR file on the local WebLogic server
mvn test #run the unit test
Congratulations, you just crafted a very convenient solution to implement your business rules. Enjoy !