Analysis of Easy Rules rule engine

What is Easy Rules?

Easy Rules is a simple and powerful Java rule engine that provides the following functions:

  • Lightweight framework and easy to learn API
  • Programming model of development and annotation based on POJO
  • Define abstract business rules and apply them easily
  • Support the ability to create composite rules from simple rules
  • Support the ability to define rules using expression languages such as MVEL and spiel

In a very interesting rule engine article In, Martin Fowler said:

You can build a simple rule engine yourself. You just need to create a set of objects with conditions and actions, store them in a collection, and run them to evaluate conditions and execute actions.

This is exactly what Easy Rules does. It provides abstract rules to create rules with conditions and actions. The Rule sEngine API runs a series of rules to evaluate conditions and execute actions.

Operating environment

Easy Rules is a Java library that needs to run on Java 1.7 and above.

maven dependency

<!--easy rules Core library-->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>3.3.0</version>
</dependency>

<!--Rule definition file format, support json,yaml etc.-->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-support</artifactId>
    <version>3.3.0</version>
</dependency>

<!--support mvel Rule Grammar Library-->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-mvel</artifactId>
    <version>3.3.0</version>
</dependency>

Define rules

Most business rules can be represented by the following definitions:

  • Name: the unique rule name in the rule namespace
  • Description: a brief description of the rule
  • Priority: the priority of the rule relative to other rules
  • Fact: a set of known facts when matching rules
  • Condition: a set of conditions that should be satisfied given certain facts in order to match the rule
  • Action: a group of actions to be performed when the conditions are met (facts can be added / deleted / modified)
    Easy Rules provides an abstraction for defining each key point of a business rule.

In Easy Rules, a Rule is represented by the Rule interface:

public interface Rule {

    /**
    * Change the conditions of method encapsulation rules
    * @return Returns true if the provided facts apply to the rule, otherwise false
    */
    boolean evaluate(Facts facts);

    /**
    * Change the method to encapsulate the actions of the rule
    * @throws If an error occurs during execution, an Exception will be thrown
    */
    void execute(Facts facts) throws Exception;

    //Getters and setters for rule name, description and priority omitted.

}

The evaluate method encapsulates a condition that must evaluate to TRUE to trigger the rule.

The execute method encapsulates the actions that should be performed when the rule conditions are met. Conditions and actions are represented by the ConditionandAction interface.

Rules can be defined in two different ways:

  • It is defined declaratively by adding comments on the POJO
  • Defined programmatically through the RuleBuilder API

1. Define rules with annotations

These are the most common ways to define rules, but you can also implement the Rulei interface or inherit the BasicRule class if necessary.

@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 of calculating rule conditions. This method must be public, can have one or more parameters annotated with @ Fact, and return Boolean types. Only one method can be annotated with @ Condition.

@The Action annotation marks the method to perform the rule Action. A rule can have multiple actions. You can use the order attribute to perform operations in the specified order. By default, the order of operations is 0.

2. Define rules with RuleBuilder API

Rule rule = new RuleBuilder()
                .name("myRule")
                .description("myRuleDescription")
                .priority(3)
                .when(condition)
                .then(action1)
                .then(action2)
                .build();

In this example, the condition instance and the Action instance are action1 and action2.

Define facts

The Facts API is an abstraction of a set of Facts that check rules. Internally, Facts instances hold HashMap < string, Object >, which means:

  • The fact needs to be named. It should have a unique name and cannot be empty
  • Any Java object can act as a fact

Here is an instance that defines the fact:

Facts facts = new Facts();
facts.add("rain", true);

Facts can be injected into rule conditions, and the action method uses @ Fact annotation. In the following rules, the rain Fact is injected into the rain parameter of itRains method:

@Rule
class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella(Facts facts) {
        System.out.println("It rains, take an umbrella!");
        // can add/remove/modify facts
    }

}

Facts type parameters are injected into known facts (like the action method takeAnUmbrella)

If the injected fact is missing, the engine will throw a RuntimeException

Define rule engine

Starting from version 3.1, Easy Rules provides two implementations of the RulesEngine interface:

  • DefaultRulesEngine: applies rules according to their natural order (priority by default).
  • InferenceRulesEngine: continue to apply rules to known facts until rules are no longer applied.

Create a rule engine

To create a rule engine, you can use the constructor for each implementation:

RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();

You can then trigger the registration rule as follows:

rulesEngine.fire(rules, facts);

Rule engine parameters

The Easy Rules engine can configure the following parameters:

Parameter	Type	Required	Default
rulePriorityThreshold	int	no	MaxInt
skipOnFirstAppliedRule	boolean	no	false
skipOnFirstFailedRule	boolean	no	false
skipOnFirstNonTriggeredRule	boolean	no	false
  • Skiponfirstapplied rule: tells the engine to skip the following rule when the rule is triggered.
  • skipOnFirstFailedRule: tells the engine to skip the following rule when the rule fails.
  • Skiponfirstnontriggered rule: tells the engine that a rule will not be triggered, skipping subsequent rules.
  • Rule priority threshold: tells the engine to skip the next rule if the priority exceeds the defined threshold. Version 3.3 does not support changing. The default is MaxInt.

You can specify these parameters using the RulesEngineParameters API:

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

If you want to get parameters from the engine, you can use the following code snippet:

RulesEngineParameters parameters = myEngine.getParameters();

This allows you to reset the engine parameters after the engine is created.

hello world example

We will create a rule that is always triggered and print "hello world" to the console when executed. The rules are as follows:

@Rule(name = "Hello World rule", description = "Always say hello world")
public class HelloWorldRule {

    @Condition
    public boolean when() {
        return true;
    }

    @Action
    public void then() throws Exception {
        System.out.println("hello world");
    }

}

Now let's create a rule engine and trigger this rule

public class Launcher {

    public static void main(String[] args) {

        // create facts
        Facts facts = new Facts();

        // create rules
        Rules rules = new Rules();
        rules.register(new HelloWorldRule());

        // create a rules engine and fire rules on known facts
        RulesEngine rulesEngine = new DefaultRulesEngine();
        rulesEngine.fire(rules, facts);

    }
}

Output results:

INFO: Engine parameters { skipOnFirstAppliedRule = false, skipOnFirstNonTriggeredRule = false, skipOnFirstFailedRule = false, priorityThreshold = 2147483647 }
INFO: Registered rules:
INFO: Rule { name = 'Hello World rule', description = 'Always say hello world', priority = '2147483646'}
INFO: Rules evaluation started
INFO: Rule 'Hello World rule' triggered
Hello world
INFO: Rule 'Hello World rule' performed successfully

ok, it's done.

Keywords: Java Spring unit testing

Added by plutomed on Mon, 27 Sep 2021 07:55:02 +0300