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.”
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)
<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.
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
- 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");
}
}
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
UnitRuleGroup speedingRuleGroup =
new UnitRuleGroup("speedingRule", "unit of speeding ticket rules");
myUnitRuleGroup.addRule(rule1);
myUnitRuleGroup.addRule(rule2);
Rules(Group of rule)
//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
- 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.
RulesEngine rulesEngine = new DefaultRulesEngine();
//fire engine using rule and fact
rulesEngine.fire(rules, facts);
Configuring the engine
- 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
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\");");
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\");"
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();
}