Introduction
Complex business rules are best implemented using a ‘Rules Engine’. Drools
is an open source Business Rules Management Product
. See here
In this blog we will cover a few basics of using the Drools rule engine, specifically using a Domain Specific Language (DSL) which is a language that is more user focused. The blog comes with a demo project which can be downloaded and used along with this document.
Demo Use Case
Our demo use case will cover evaluating an ‘Application Form‘ with multiple ‘Sections‘
Each form section has a ‘rule‘ which the current form evaluators (manual task) use to evaluate the ‘Questions‘ in the form. Each form question has one or more ‘option‘ selected.
For example:
Form - Section1 - Question1 - Option1 - Question2 - OptionA,OptionB - Section2 - Question1 - OptionX,OptionY
Now let us assume a use case with a few simple questions and conditions associated with a particular form, for example, a ‘weekend work approval’ form. We can as a few simple questions
Form:
- Section1:
- Question1: “Is this necessary work”
- options: [Yes, No]
- rule: “Approved if Yes is selected”
- Question1: “Is this necessary work”
- Section2:
- Question1: “When is this work to be done”
- options: [Weekend Work, Regular time]
- rule: “Manager approval required if this is weekend work“
- Question1: “When is this work to be done”
- Section3:
- Question1: “Is this an emergency”
- options: [Non-Emergency, Emergency]
- rule: “If this is an emergency fix then it is approved“
- Question1: “Is this an emergency”
As you can see in our sample use case, we have only one question per section but this can be more.
Code Repository
You can download the source code from here using
git@bitbucket.org:arshimkola/drools-forms-demo.git
Execution Instructions
Run the form.demo.rules.RulesExecutor
java main to run the demo
First Steps – a simple condition
A Simple Rule is implemented in file called rule.dslr
package form.demo.rules; import form.demo.rules.facts.* import function form.demo.rules.RulesLogger.log expander rule.dsl // ---------------------------------------------------- // Rule #1 // ---------------------------------------------------- rule "Section1 Rule1.1" when Form has a section called "Section1" then Section outcome is "No Further Review Required" end
The DSLR file imports facts from the package form.demo.rules.facts
. There is a function called log
defined in form.demo.rules.RulesLogger
class. There is an expander for the DSLR that converts the expander rule.dsl
is defined as the expander
The DSL for the rule is in the rule.dsl
file
#--------------------------------------------------------------------------------------- # Rule DSL #--------------------------------------------------------------------------------------- [when]Form has a section called {name}=$form:FormFact(getSection({name}) != null) [when] And = and [when] OR = or [then]Section outcome is {outcome}=$form.getSection({name}).setOutcome({outcome});log(drools,"Section:"+{name}+", Outcome:"+{outcome}+", Rule Applied:"+ drools.getRule().getName() );
When executed against a set of Facts
FormFact formWithFacts = new FormFact(); formWithFacts.addSection("Section1", "Question1", "Yes"); FormAssessmentInfo assessmentInfo = new RulesExecutor().eval(formWithFacts);
Dec 01, 2015 3:49:09 PM form.demo.rules.RulesLogger log INFO: Rule:"Section1 Rule1.1", Matched --> [ Section:Section1, Outcome:No Further Review Required, Rule Applied:Section1 Rule1.1] Not Evaluated Section1->No Further Review Required
Adding a second condition
– If some option is selected in a section then set the outcome to a value
// ---------------------------------------------------- // Rule #1 // ---------------------------------------------------- rule "Section1 Rule1.1" when Form has a section called "Section1" -"Yes" is ticked in "Question1" then Section outcome is "No Further Review Required" end
#--------------------------------------------------------------------------------------- # Rule DSL #--------------------------------------------------------------------------------------- [when]Form has a section called {name}=$form:FormFact(getSection({name}) != null) [when]-{option} is ticked in {question}=eval($form.getSection({name}).has({question},{option})) [when] And = and [when] OR = or [then][Form]Section outcome is {outcome}=$form.getSection({name}).setOutcome({outcome});log(drools,"Section:"+{name}+", Outcome:"+{outcome}+", Rule Applied:"+ drools.getRule().getName() );
Adding Global Rules
- If a section acts as a global flag (for example: Emergency Approval) then ignore all outcomes and select this
- If there is no global flag then if any of the sections have outcome ‘foo’ then set the form outcome to ‘bar’ otherwise set the form outcome to ‘baz’
In the Rule DSL we add the following, notice how a new instance of the FormFact is created – this time without matching a section name
[when]The Form=$form:FormFact() [when]-has a section with outcome {outcome}=eval($form.hasSectionWithOutcome({outcome})) [when]-has no section with outcome {outcome}=eval($form.hasSectionWithOutcome({outcome}) == false) [then]Form outcome is {outcome}=$form.setOutcome({outcome});log(drools,"Form Outcome Set to "+{outcome});
In the DSLR we implement a few global rules
// ---------------------------------------------------- // Global Rule #1 // ---------------------------------------------------- rule "Global Rule1.1" when The Form -has a section with outcome "Emergency Work" then Form outcome is "Approved" end // ---------------------------------------------------- // Global Rule #2.1 // ---------------------------------------------------- rule "Global Rule2.1" when The Form -has no section with outcome "Emergency Work" -has a section with outcome "Manager Review Required" then Form outcome is "Manager Review Required" end // ---------------------------------------------------- // Global Rule #2.2 // ---------------------------------------------------- rule "Global Rule2.2" when The Form -has no section with outcome "Emergency Work" -has no section with outcome "Manager Review Required" then Form outcome is "Manager Review Required" end