-
Notifications
You must be signed in to change notification settings - Fork 1.1k
defining rules
Most business rules can be represented by the following definition:
- Name: a unique rule name within a rules namespace
- Description: a brief description of the rule
- Priority: rule priority regarding to other rules
- Facts: set of known facts at the time of firing rules
- Conditions: set of conditions that should be satisfied given some facts in order to apply the rule
- Actions: set of actions to perform when conditions are satisfied (and that may add/remove/modify facts)
Easy Rules provides an abstraction for each of these key points to define business rules.
A rule in Easy Rules is represented by the Rule
interface:
public interface Rule extends Comparable<Rule> {
/**
* This method encapsulates the rule's conditions.
* @return true if the rule should be applied given the provided facts, false otherwise
*/
boolean evaluate(Facts facts);
/**
* This method encapsulates the rule's actions.
* @throws Exception if an error occurs during actions performing
*/
void execute(Facts facts) throws Exception;
//Getters and setters for rule name, description and priority omitted.
}
The evaluate
method encapsulates conditions that must evaluate to TRUE to trigger the rule.
The execute
method encapsulates actions that should be performed when rule's conditions are satisfied.
Conditions and actions are represented by the Condition
and Action
interfaces.
Note how Rule
extends Comparable
. This is because rules will be compared to each other when they are registered in a rules namespace. Please refer to the Rules API section for more details.
Rules can be defined in two different ways:
- Declaratively by adding annotations on a POJO
- Programmatically through the
RuleBuilder
API
Those are the most common ways to define rules, but you can also implement the Rule
interface or extend the BasicRule
class if you want.
Easy Rules provides the @Rule
annotation that can turn a POJO into a rule. Here is an example:
@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {
@Condition
public boolean when(@Fact("fact") fact) {
//my rule conditions
return true;
}
@Action(order = 1)
public void then(Facts facts) throws Exception {
//my actions
}
@Action(order = 2)
public void finally() throws Exception {
//my final actions
}
}
The @Condition
annotation marks the method to execute to evaluate the rule conditions.
This method must be public, may have one or more parameters annotated with @Fact
and return a boolean type. Only one method can be annotated with @Condition
annotation.
The @Action
annotation marks methods to execute to perform rule actions. Rules can have multiple actions. Actions can be executed in a specified order using the order attribute. By default, the order of an action is 0.
The RuleBuilder
allows you to define rules with a fluent API:
Rule rule = new RuleBuilder()
.name("myRule")
.description("myRuleDescription")
.priority(3)
.when(condition)
.then(action1)
.then(action2)
.build();
In this example, condition
in an instance of Condition
and action1
and action2
are instances of Action
.
Easy Rules allows you to create complex rules from primitive ones. A CompositeRule
is composed of a set of rules. This is typically an implementation of the composite design pattern.
A composite rule is an abstract concept since composing rules can be triggered in different ways. Easy Rules comes with 3 implementations of CompositeRule
that can be found in the 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.
Composite rules can be created from primitive rules and registered as regular rules:
//Create a composite rule from two primitive rules
UnitRuleGroup myUnitRuleGroup =
new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);
//Register the composite rule as a regular rule
Rules rules = new Rules();
rules.register(myUnitRuleGroup);
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);
Heads up: The rules engine works against the Rule
interface and sees composite rules as regular rules. For this reason, the RuleListener
is called around the composite rule and not around its composing rules. This is inherent to the composite design pattern.
❗ Please note that CompositeRule
and its sub-classes are not thread-safe.
Each rule in Easy Rules has a priority. This represents the default order in which registered rules are fired.
By default, lower values represent higher priorities.
To override this behavior, you should override the compareTo
method to provide a custom priority strategy.
-
If you decided to extend the
BasicRule
class, you can specify rule priority at construction time or by overriding thegetPriority()
method -
If your rule is a annotated POJO, you can provide the priority through the
priority
attribute of the@Rule
annotation. Since this attribute is static, you can override it with a method annotated with@Priority
annotation. This method must be public, have no arguments and return anInteger
type. -
If you use the
RuleBuilder
to define your rule, you can specify the rule's priority using theRuleBuilder#priority
method
A set of rules in Easy Rules is represented by the Rules
API. It can be used as follows:
Rules rules = new Rules();
rules.register(myRule1);
rules.register(myRule2);
Rules
represents a namespace for registered rules. Hence, each registered rule must have a unique name within this namespace.
Heads up: Rules will be compared to each other based on Rule#compareTo()
method, so Rule
's implementations are expected to correctly implement compareTo
to ensure unique rule names within a single namespace.
Easy Rules is created by Mahmoud Ben Hassine with the help of some awesome contributors
-
Introduction
-
User guide
-
Tutorials
-
Get involved