Skip to content

defining rules

Mahmoud Ben Hassine edited this page Dec 6, 2020 · 23 revisions

Introduction

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.

Defining rules using annotations

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.

Defining rules using the RuleBuilder API

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.

Composite rules

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.

Rules priorities

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 the getPriority() 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 an Integer type.

  • If you use the RuleBuilder to define your rule, you can specify the rule's priority using the RuleBuilder#priority method

The Rules API

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.