Drools JBoss Rules 5.0 Developer’s Guide
Business rules and processes can help your business by providing a level of agility and
flexibility. As a developer, you will be largely responsible for implementing these
business rules and processes effectively, but implementing them systematically can often
be difficult due to their complexity. Drools, or JBoss Rules, makes the process of
implementing these rules and processes quicker and handles the complexity, making your
life a lot easier!
This book guides you through various features of Drools, such as rules, ruleflows,
decision tables, complex event processing, Drools Rete implementation with various
optimizations, and others. It will help you to set up the Drools platform and start creating
your own business. It's easy to start developing with Drools if you follow our real-world
examples that are intended to make your life easier.
Starting with an introduction to the basic syntax that is essential for writing rules, the
book will guide you through validation and human-readable rules that define, maintain,
and support your business agility. As a developer, you will be expected to represent
policies, procedures, and constraints regarding how an enterprise conducts its business;
this book makes it easier by showing you the ways in which it can be done.
A real-life example of a banking domain allows you to see how the internal workings of
the rules engine operate. A loan approval process example shows the use of the Drools
Flow module. Parts of a banking fraud detection system are implemented with Drools
Fusion module, which is the Complex Event Processing part of Drools. This in turn, will
help developers to work on preventing fraudulent users from accessing systems in an
illegal way.
Finally, more technical details are shown on the inner workings of Drools, the
implementation of the ReteOO algorithm, indexing, node sharing, and partitioning.
What This Book Covers
Chapter 1: This chapter introduces the reader to the domain of business rules and
business processes. It talks about why the standard solutions fail at implementing
complex business logic. It shows a possible solution in the form of a declarative
programming model. The chapter talks about advantages and disadvantages of Drools.
A brief history of Drools is also mentioned.
Chapter 2: This chapter shows us the basics of working with the Drools rule
engine—Drools Expert. It starts with a simple example that is explained step-by-step.
It begins with the development environment setup, writing a simple rule, and then
executing it. The chapter goes through some necessary keywords and concepts that are
needed for more complex examples.
Chapter 3: This chapter introduces the reader to a banking domain that will be the basis
for examples later in this book. The chapter then goes through an implementation of a
decision service for validating this banking domain. A reporting model is designed that
holds reports generated by this service.
Chapter 4: This chapter shows how Drools can be used for carrying out complex data
transformation tasks. It starts with writing some rules to load the data, continues with the
implementation of various transformation rules, and finally puts together the results of
this transformation. The chapter shows how we can work with a generic data structure
such as a map in Drools.
Chapter 5: The focus of this chapter is on rules that are easy to read and change. Starting
with domain specific languages, the chapter shows how to create a data transformation
specific language. Next, it focuses on decision tables as another more user-friendly way
of representing business rules. An interest rate calculation example is shown. Finally, the
chapter introduces the reader to Drools Flow module as a way of managing the rule
execution order.
Chapter 6: This chapter talks about executing the validation decision service in a stateful
manner. The validation results are accumulated between service calls. This shows another
way of interacting with a rule engine. Logical assertions are used to keep the report up-todate.
Various ways of serializing a stateful session are discussed.
Chapter 7: This chapter talks about Drools Fusion—another cornerstone of the
Drools platform is about writing rules that react to various events. The power of Drools
Fusion is shown through a banking fraud detection system. The chapter goes through
various features such as events, type declarations, temporal operators, sliding windows,
and others.
Chapter 8: This chapter goes into more detail about the workflow aspect of the Drools
platform. It is showed through a loan approval service that demonstrates the use of
various nodes in a flow. Among other things, the chapter talks about implementing a
custom work item, human task, or a sub-flow.
Chapter 9: The purpose of this chapter is to show you how to integrate Drools in a real
web application. We'll go through design and implementation of persistence, business
logic, and presentation layers. All of the examples written so far will be integrated into
this application.
Chapter 10: The focus of this chapter is to give you an idea about the various
ways of testing your business logic. Starting with unit testing, integration testing
through acceptance testing that will be shown with the help of the Business Rules
Management Server—Guvnor, this chapter provides useful advice on various
troubleshooting techniques.
Chapter 11: This chapter shows integration with the Spring Framework. It describes how
we can make changes to rules and processes while the application runs. It shows how to
use an external build tool such as Ant to compile rules and processes. It talks about the
rule execution server that allows us to execute rules remotely. It briefly mentions support
of various standards.
Chapter 12: This chapter goes under the hood of the Drools rule engine. By
understanding how the technology works, you'll be able to write more efficient rules
and processes. It talks about the ReteOO algorithm, node sharing, node indexing, and
rule partitioning for parallel execution.
Human-readable Rules
Business rules implementations presented so far were aimed mostly at developers.
However, it is sometimes needed that these rules are readable and understandable
by the business analysts. Ideally, they should be able to change the rules or even
write new ones. An important aspect of business rules is their readability and user
friendliness. Looking at a rule, you should immediately have an idea of what it is
about. In this chapter, we'll look at Domain Specific Languages (DSLs), decision
tables, and rule flows to create human-readable rules.
Domain Specific Language
The domain in this sense represents the business area (for example, life insurance
or billing). Rules are expressed with the terminology of the problem domain. This
means that domain experts can understand, validate, and modify these rules
more easily.
You can think of DSL as a translator. It defines how to translate sentences from the
problem-specific terminology into rules. The translation process is defined in a .dsl
file. The sentences themselves are stored in a .dslr file. The result of this process
must be a valid .drl file.
Building a simple DSL might look like:
[condition][]There is a Customer with firstName
{name}=$customer : Customer(firstName == {name})
[consequence][]Greet Customer=System.out.println("Hello " +
$customer.getFirstName());
Code listing 1: Simple DSL file simple.dsl.
The code listing above contains only two lines (each begins with [).
However, because the lines are too long, they are wrapped effectively
creating four lines. This will be the case in most of the code listings.
When you are using the Drools Eclipse plugin to write this DSL, enter the
text before the first equal sign into the field called Language expression,
the text after equal sign into Rule mapping, leave the object field blank
and select the correct scope.
The previous DSL defines two DSL mappings. They map a DSLR sentence to a DRL
rule. The first one translates to a condition that matches a Customer object with
the specified first name. The first name is captured into a variable called name. This
variable is then used in the rule condition. The second line translates to a greeting
message that is printed on the console. The following .dslr file can be written based
on the previous DSL:
package droolsbook.dsl;
import droolsbook.bank.model.*;
expander simple.dsl
rule "hello rule"
when
There is a Customer with firstName "David"
then
Greet Customer
end
Code listing 2: Simple .dslr file (simple.dslr) with rule that greets a customer with
name David.
As can be seen, the structure of a .dslr file is the same as the structure of a .drl
file. Only the rule conditions and consequences are different. Another thing to note
is the line containing expander simple.dsl. It informs Drools how to translate
sentences in this file into valid rules. Drools reads the simple.dslr file and tries to
translate/expand each line by applying all mappings from the simple.dsl file (it does it in
a single pass process, line-by-line from top to bottom). The order of lines is important
in a .dsl file. Please note that one condition/consequence must be written on one
line, otherwise the expansion won't work (for example, the condition after the when
clause, from the rule above, must be on one line).
When you are writing .dslr files, consider using the Drools Eclipse plugin. It
provides a special editor for .dslr files that has an editing mode and a read-only
mode for viewing the resulting .drl file. A simple DSL editor is provided as well.
The result of the translation process will look like the following screenshot:
This translation process happens in memory and no .drl file is physically stored.
We can now run this example. First of all, a knowledge base must be created from
the simple.dsl and simple.dslr files. The process of creating a package using a
DSL is as follows (only the package creation is shown, the rest is the same as we've
seen in Chapter 2, Basic Rules):
KnowledgeBuilder acts as the translator. It takes the .dslr file, and based on the
.dsl file, creates the DRL. This DRL is then used as normal (we don't see it; it's
internal to KnowledgeBuilder). The implementation is as follows:
private KnowledgeBase createKnowledgeBaseFromDSL()
throws Exception {
KnowledgeBuilder builder =
KnowledgeBuilderFactory.newKnowledgeBuilder();
builder.add(ResourceFactory.newClassPathResource(
"simple.dsl"), ResourceType.DSL);
builder.add(ResourceFactory.newClassPathResource(
"simple.dslr"), ResourceType.DSLR);
if (builder.hasErrors()) {
throw new RuntimeException(builder.getErrors()
.toString());
}
KnowledgeBase knowledgeBase = KnowledgeBaseFactory
.newKnowledgeBase();
knowledgeBase.addKnowledgePackages(
builder.getKnowledgePackages());
return knowledgeBase;
}
Code listing 3: Creating knowledge base from .dsl and .dslr files.
The .dsl and subsequently the .dslr files are passed into KnowledgeBuilder. The
rest is similar to what we've seen before.
DSL as an interface
DSLs can be also looked at as another level of indirection between your .drl files
and business requirements. It works as shown in the following figure:
The figure above shows DSL as an interface (dependency diagram). At the top are
the business requirements as defined by the business analyst. These requirements
are represented as DSL sentences (.dslr file). The DSL then represents the interface
between DSL sentences and rule implementation (.drl file) and the domain model.
For example, we can change the transformation to make the resulting rules more
efficient without changing the language. Further, we can change the language, for
example, to make it more user friendly, without changing the rules. All this can be
done just by changing the .dsl file.
DSL for validation rules
The first three implemented object/field required rules from Chapter 2, Basic Rules,
can be rewritten as:
- If the Customer does not have an address, then Display warning message
- If the Customer does not have a phone number or it is blank, then Display
error message
- If the Account does not have an owner, then Display error message
for Account
We can clearly see that all of them operate on some object (Customer/Account),
test its property (address/phone/owner), and display a message (warning/error)
possibly with some context (account). Our validation.dslr file might look like the
following code:
expander validation.dsl
rule "address is required"
when
The Customer does not have address
then
Display warning
end
rule "phone number is required"
when
The Customer does not have phone number or it is blank
then
Display error
end
rule "account owner is required"
when
The Account does not have owner
then
Display error for Account
end
Code listing 4: First DSL approach at defining the required object/field rules
(validation.dslr file).
The conditions could be mapped like this:
[condition][]The {object} does not have {field}=${object} : {object}(
{field} == null )
Code listing 5: validation.dsl.
This covers the address and account conditions completely. For the phone
number rule, we have to add the following mapping at the beginning of the
validation.dsl file:
[condition][] or it is blank = == "" ||
Code listing 6: Mapping that checks for a blank phone number.
As it stands, the phone number condition will be expanded to:
$Customer : Customer( phone number == "" || == null )
Code listing 7: Unfinished phone number condition.
To correct it, phone number has to be mapped to phoneNumber. This can be done by
adding the following at the end of the validation.dsl file:
[condition][]phone number=phoneNumber
Code listing 8: Phone number mapping.
The conditions are working. Now, let's focus on the consequences. The following
mapping will do the job:
[consequence][]Display {message_type} for {object}={message_type}(
kcontext, ${object} );
[consequence][]Display {message_type}={message_type}( kcontext );
Code listing 9: Consequence mappings.
The three validation rules are now being expanded to the same .drl representation
as we've seen in Chapter 2.
File formats
Before we go further, we'll examine each file format in more detail.
DSL file format
A l ine in a .dsl file has the following format:
[<scope>][<Type>]<language expression>=<rule mapping>
Code listing 10: The format of one line in a .dsl file.
As we've already seen, an example of a line in DSL file might look like this:
[condition][droolsbook.bank.model.Customer]The Customer does not have
address=Customer(address == null)
Code listing 11: Sample line from DSL file (note that it is just one line that has
been wrapped).
The scope can have the following values:
- condition: Specifies that this mapping can be used in the condition part of
a rule.
- consequence: Specifies that this mapping can be used in the consequence
part of a rule.
- *: Specifies that this mapping can be used in both the condition and the
consequence part of a rule.
- keyword: This mapping is applied to the whole file (not just the condition
or the consequence part). Used mainly when writing DSLs in languages
other than English or to hide the package/import/global statements at the
beginning of the file behind a business friendly sentence.
Type can be used to further limit the scope of the mapping. Scope and Type are used
by the Drools Eclipse plugin to provide auto-completion when writing .dslr files
(when pressing Ctrl + Space, only relevant choices are offered). This is especially
useful with the multiple constraints feature (refer to the section, DSL for multiple
constraints in a condition).
DSL supports comments by starting the line with the hash character, #. For example:
#this is a comment in a .dsl file
RL file format
As a side note, in a .drl file, it is valid to write the whole rule on a single line. This
allows us to write more complex DSLs because one sentence in .dslr file can be
translated into multiple conditions—even the whole rule. For example, these are
valid rules on a single line:
rule "addressRequired" when Customer( address == null ) then
warning(kcontext); end
Code listing 12: addressRequired rule on one line.
Make sure that you add spaces between Drools keywords. Another more complex
example of a rule on one line:
rule "studentAccountCustomerAgeLessThan" when Customer( eval (year
sPassedSince(dateOfBirth) >= 27) ) and $account : Account( type ==
Account.Type.STUDENT ) then error(kcontext, $account); System.out.
println("another statement"); end
Code listing 13: studentAccountCustomerAgeLessThan rule on one line.
The preceding rule contains two conditions and two Java statements in the
consequence block. There is also an optional and keyword between the conditions
to make it more readable.
DSLR file format
A . dslr file contains the sentences written using the DSL. The .dslr file is very
similar to the .drl file. One thing to note is that by prepending a line with a '>', we
can turn off the expander for the line. This allows us to write a hybrid .dslr file that
contains traditional DRL rules and DSL rules. For example, if we are not yet sure
how to map some complex rule, we can leave it in its original .drl file format.
DSL for multiple constraints in a condition
We' ll go through more complex DSLs. Let's look at a standard condition for example:
Account( owner != null, balance > 100, currency == "EUR" )
Code listing 14: Condition that matches some account.
It is difficult to write DSL that will allow us to create conditions with any subset
of constraints from the code listing above (without writing down all possible
permutations). The '-' feature comes to the rescue:
[condition][]There is an Account that=$account : Account( )
[condition][]-has owner=owner != null
[condition][]-has balance greater than {amount}=balance > {amount}
[condition][]-has currency equal to {currency}=currency == {currency}
Code listing 15: DSL using the '-' feature. This can create seven combinations of
the constraints.
When the DSL condition starts with '-', the DSL parser knows that this constraint
should be added to the last condition (in a .dslr file). With the preceding DSL, the
following condition can be created:
There is an Account that
- has currency equal to "USD"
"has balance greater than 2000"
Code listing 16: Condition using the '-' feature (in a .dslr file).
The '-' feature increases the fl exibility of the resulting language. It works just fine
for simple cases involving only one pair of brackets. In case of multiple brackets in
the condition, Drools always adds the constraint to the last pair of brackets. This may
not always be what we want. We have to find a different way of specifying multiple
constraints in a condition. We can also write our DSL in the following manner:
[condition][]There is an Account that {constraints} = Account(
{constraints} )
[condition][]has {field} equal to {value}={field} == {value}
[condition][]and has {field} equal to {value}=, {field} == {value}
Code listing 17: Flexible DSL that can be expanded to a condition with two
field constraints.
With this DSL, the following DSLR can be written:
There is an Account that has owner equal to null and has balance equal
to 100
Code listing 18: DSLR that describes an account with two constraints.
If we want to have more conditions, we can simply duplicate the last line in the DSL.
Remember? Translation is a single pass process.
Named capture groups
Som etimes, when a more complex DSL is needed, we need to be more precise at
specifying what a valid match is. We can use named capture groups with regular
expressions to give us the needed precision. For example:
{name:[a-zA-Z]+}
Code listing 19: Name that matches only characters.
Regular expressions (java.util.regex.Pattern) can be used not
only for capturing variables but also within the DSL. For example, in
order to carry out case insensitive matching. If we look at the DSL from
code listing 15, the users should be allowed to type Account, account,
ACCOUNT, or even aCcount in their .dslr files. This can be done by
enabling the embedded case insensitive fl ag expression—(?i):
[condition][]There is an (?i:account) that ....
Another useful example is sentences that are sensitive to
gender—(s)?he to support "he" and "she", and so on.
In order to make the sentences space insensitive, Drools automatically
replaces all spaces with \s+. Each \s+ matches one or more spaces. For
example, the following line in a .dslr file will be successfully expanded
by the DSL from code listing 15:
There is an Account that ....
DSL for data transformation rules
We' ll now implement DSL for the data transformation rules from Chapter 4, Data
Transformation. We'll reuse our rule unit tests to verify that we don't change the
functionality of the rules but only their representation. The unit test class will be
extended and the method for creating KnowledgeBase will be overridden to use the
.dsl file and .dslr file as inputs. Rule names will stay the same. Let's start with the
twoEqualAddressesDifferentInstance rule:
rule twoEqualAddressesDifferentInstance
when
There is legacy Address-1
There is legacy Address-2
- same as legacy Address-1
then
remove legacy Address-2
Display WARNING for legacy Address-2
end
Code listing 20: Rule for removing redundant addresses
(dataTransformation.dslr file).
The conditions can be implemented with the following DSL:
[condition][] legacy {object}-{id} = {object}-{id}
[condition][] There is {object}-{id} = ${object}{id} : Map( this["_
type_"] == "{object}" )
[condition][]- same as {object}-{id} = this == ${object}{id}, eval(
${object}1 != ${object}2 )
Code listing 21: DSL for conditions (dataTransformation.dsl file).
The first mapping is a simple translation rule, where we remove the word legacy.
The next mapping captures a map with its type. The last mapping includes the
equality test with the object identity test. Mapping for consequences is as follows:
[consequence][] legacy {object}-{id} = ${object}{id}
[consequence][]Display {message_type_enum} for {object}=validationRepo
rt.addMessage(reportFactory.createMessage(Message.Type.{message_type_
enum}, kcontext.getRule().getName(), {object}));
[consequence][]remove {object} = retract( {object} );
Code listing 22: DSL for consequences.
The first mapping just removes the word legacy. The second mapping
adds a message to validationReport. Finally, the last mapping
removes an object from the knowledge session. This is all we need for the
twoEqualAddressesDifferentInstance rule.
As you can see, we started with the sentence in the domain specific language
(code listing 1) and then we've written the transformation to refl ect the rules (from
Chapter 4). In reality, this is an iterative process. You'll modify the .dslr and .dsl
files until you are happy with the results. It is also a good idea to write your rules in
standard .drl first and only then try to write a DSL for them.
We 'll move to the next rule, addressNormalizationUSA:
rule addressNormalizationUSA
when
There is legacy Address-1
- country is one of "US", "U.S.", "USA", "U.S.A"
then
for legacy Address-1 set country to USA
end
Code listing 23: DSLR rule for normalizing address country field.
The rule just needs another constraint type:
[condition][]- country is one of {country_list} = this["country"] in
({country_list})
Code listing 24: Another condition mapping.
The consequence is defined with two mappings. The first one will translate the
country to an enum and the second will then perform the assignment.
[consequence][]set country to {country}=set country to Address.
Country.{country}
[consequence][]for {object}set {field} to {value} = modify( {object} )
\{ put("{field}", {value} ) \}
Code listing 25: Consequence mapping for the country normalization rule.
Please note that the curly brackets are escaped. Moreover, the original rule used mvel
dialect. It is a good idea to write your rules using the same dialect. It makes the DSL
easier. Otherwise, the DSL will have to be "dialect aware".
The other country normalization rule can be written without modifying the DSL.
We'll now continue with unknownCountry rule:
rule unknownCountry
Apply after address normalizations
when
There is legacy Address-1
- country is not normalized
then
Display ERROR for legacy Address-1
end
Code listing 26: DSLR representation of the unknownCountry rule.
The whole sentence Apply after address normalizations is mapped as a
keyword mapping:
[keyword][] Apply after address normalizations = salience -10
Code listing 27: salience keyword mapping.
Now, we can use the other rule attributes to achieve the same goal just by changing
the DSL.
Additional mapping that is needed:
[condition][]- country is not normalized = eval(!($Address1.
get("country") instanceof Address.Country))
Code listing 28: Another condition mapping.
In the condition mapping, the $Address1 is hard-coded. This is fine for the rules that
we have.
As you can imagine, the rest of the rules follow similar principles.
What we have achieved by writing this DSL is better readability. A business analyst
can verify the correctness of these rules more easily. We could push this further by
defining a complete DSL that can represent any concept from the problem domain.
The business analyst will then be able to express any business requirement just by
editing the .dslr file. |