Spring java Rule Engine - Jeasy Rule Examples

We may have faced a situation where there will be a lot of conditions which need to be satisfied causing lot of if else if conditions in the code even if we want to add new condition we have of add extra if conditions in code which make our code less configurable and readable.

To avoid such lot of if else if conditions and make our code more configurable we can use a third party library such a Jeasy Rule which is a rule engine.

Using rule engine, we can move all our conditions(rules) to external yams file and make changes only to these yaml file making our code more configurable.

So Today we will see Jeasy rule with some examples.

What is Jeasy Rule?

JEasy Rules is a Java rules engine which is inspired by an article called "Should I use a Rules Engine?" of Martin Fowler in which Martin says:

“You can build a simple rules engine yourself. All you need is to create a bunch of objects with conditions and actions, store them in a collection, and run through them to evaluate the conditions and execute the actions.”

Jeasy java rule engine spring boot example tutorial

Features of Jeasy rule engine

  • Lightweight library and easy to learn API.
  • POJO based development also annotation based programming.
  • Useful abstractions to define business rules and apply them easily with Java
  • The ability to create composite rules from primitive ones
  • Ability to create rules using other Expression language(SpEL,MVEL,JEXL)

First add maven dependency
    
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>4.0.0</version>
</dependency>
    

All easy support module maven dependency can be found here Maven dependency


Defining Fact

A fact is name-value pairs in Easy rule where both name and value must not be null and each fact should contain unique name this fact are used in rules Where easy rule engine apply fact again rule and evaluate them.

Example generic fact

Fact<String> speed = new Fact("speed", "60");
Facts facts = new Facts();
facts.add(speed);
 
//shorter version
Facts facts = new Facts();
facts.put("speed", "60");

Defining rules

Most business rules can be represented by the following definitions:

  • Name: unique rule name under a namespace
  • Description: brief description of the rule
  • Priority: priority relative to other rules
  • Facts: facts, data to be processed immediately
  • Conditions: a set of conditions that must be met in order to apply rules
  •  Actions: a set of actions to be executed when the conditions are met

We can define rules in two ways, Using annotation on POJO class and programmatically using API. 


Let take a problem below and try to define a few rule.


You are driving a little too fast, and a police officer stops you. Write code to compute the result, encoded as an int value: 0=no ticket, 1=small ticket, 2=big ticket. If speed is 60 or less, the result is 0. If speed is between 61 and 80 inclusive, the result is 1. If speed is 81 or more, the result is 2. 


Annotation based Rules 

    
    @Rule(name = "speedingRule", description = "if you are over speed then ticket you")
public class SpeedRule{
 
@Condition
public boolean isSpeeding(@Fact("speed") int speed) {
return speed <= 60;
}
 
@Action(order=1)
public void ticketHim() {
System.out.println("Ticket price 0");
}
}
Only one method can be annotated with condition but rule can have multiple action each will be executed by the specified order.

RuleBuilder API


Rule speedingRule = new RuleBuilder().name("Speeding Ticket Rule1")
.description("if police caught you Speeding then ticket you based on your speed")
.priority(0)
.when(condition)
.then(action)
.build();


Combining rules together

We can group or combine multiple rules to composite rule. There are 3 ways to create composite rule in Easy Rules using easy-rules-support module.

UnitRuleGroup: A unit rule group is a composite rule that acts as a unit: Either all rules are applied or nothing is applied.

ActivationRuleGroup: An activation rule group is a composite rule that fires the first applicable rule and ignores other rules in the group (XOR logic). Rules are first sorted by their natural order (priority by default) within the group.

ConditionalRuleGroup: A conditional rule group is a composite rule where the rule with the highest priority acts as a condition: if the rule with the highest priority evaluates to true, then the rest of the rules are fired.


UnitRuleGroup speedingRuleGroup =
    new UnitRuleGroup("speedingRule", "unit of speeding ticket rules");
myUnitRuleGroup.addRule(rule1);
myUnitRuleGroup.addRule(rule2);


Rules(Group of rule)

Rules API contains all the set of rules in Easy Rules. A rule or rule group must be registered with Rules which easy rule evaluate.


//registering each rule one by one
Rules rules = new Rules();
rules.register(speedingRule1);
rules.register(speedingRule2);
rules.register(speedingRule3);

Or 
 
//registering rule group
rules.register(speedingRuleGroup);

Defining RuleEngine

We have rule and fact now so we need a ruleEngine where our fact will be applied again rule and engine tell which rule passes based on fact.

Easy Rules provides two implementations of the RulesEngine interface:


  • DefaultRulesEngine: applies rules according to their natural order (which is priority by default).
  • InferenceRulesEngine: continuously applies rules on known facts until no more rules are applicable.

To create ruleengine

RulesEngine rulesEngine = new DefaultRulesEngine();
//fire engine using rule and fact
rulesEngine.fire(rules, facts);

Configuring the engine

Using RulesEngineParameters class help us to configure the rule engine using pre-defined property.

  • skipOnFirstAppliedRule parameter tells the engine to skip next rules when a rule is applied.
  • skipOnFirstFailedRule parameter tells the engine to skip next rules when a rule fails.
  • skipOnFirstNonTriggeredRule parameter tells the engine to skip next rules when a rule is not triggered.
  • rulePriorityThreshold parameter tells the engine to skip next rules if priority exceeds the defined threshold.



RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

Using Expression language to define Rules

Easy Rules provides support for defining rules with MVEL, SpEL and JEXL ,easy rules provided separeted maven dependency for each one which we have to add in our project.

Let define rule using MVEL

Rule speedingRule1 = new MVELRule().name("Speeding Ticket Rule1")
.description("if police caught you Speeding then ticket you based on your speed")
.priority(0)
.when("speed <=60")
.then("System.out.println(\"Ticket = 0\");");
 
Rule speedingRule2 = new MVELRule().name("Speeding Ticket Rule2").priority(1).when("speed >=61 && speed <=80")
.then("System.out.println(\"Ticket = 1\");");
 
Rule speedingRule3 = new MVELRule().name("Speeding Ticket Rule3").priority(2).when("speed >=81").then("System.out.println(\"Ticket = 2\");");

We can also define a separated yaml file containing all rule condition and their action


name: "Speeding Rule1"
description: "if it rains then take an umbrella"
priority: 0
condition: "speed <=60"
actions:
  - "System.out.println(\"Ticket = 0\");"
---
name: "weather rule"
description: "if it rains then take an umbrella"
priority: 0
condition: "speed >=61 && speed <=80"
actions:
  - "System.out.println(\"Ticket = 1\");"
--- 
name: "weather rule"
description: "if it rains then take an umbrella"
priority: 0
condition: "speed >=81"
actions:
  - "System.out.println(\"Ticket = 2\");"
Now we have to load the yaml using ruleFactory and reads rules from them. Below is an complete example

try {
 
//Define Facts
Facts facts = new Facts();
facts.put("speed", 60);
 
//Define rules
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
 
//load the file
File file=ResourceUtils.getFile("classpath:speeding-rules.yaml");
Rules rules = ruleFactory.createRules(new FileReader(file));
// fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
} 
catch(Exception e) {
e.printStackTrace();
}

Post a Comment

Previous Post Next Post

Recent Posts

Facebook