What is “Easy Rules”?

Easy Rules is a simple yet powerful Java Rules engine that provides the following features:

  • Lightweight framework and easy-to-learn API
  • Pojo-based development and annotation programming model
  • Define abstract business rules and apply them easily
  • Supports the ability to create composite rules from simple rules
  • Support for the ability to define rules using expression languages such as MVEL and SpEL

In a very interesting article on rules engines, Martin Fowler says:

You can build a simple rules engine yourself. All you need to do is create a set of objects with conditions and actions, store them in a collection, and run them to evaluate conditions and perform actions.

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

Runtime environment

Easy Rules is a Java library that needs to run in Java 1.7 or above.

Maven rely on

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

<! -- Rule defines file format, supports JSON, YAML, etc. -->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-support</artifactId>
    <version>3.3.0</version>
</dependency>

<! -- support mVEL rule syntax library -->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-mvel</artifactId>
    <version>3.3.0</version>
</dependency>
Copy the code

Define the 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 rules
  • Priority: Rule priority relative to other rules
  • Facts: a set of known facts to match a rule
  • Conditions: A set of conditions that should be met given certain facts in order to match this rule
  • Action: a set of actions to be performed when a condition is met (facts can be added/deleted/modified)

Easy Rules provides an abstraction for each of the key points that define business Rules.

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

public interface Rule {

    /** * Conditions *@returnReturn true if the facts provided apply to this rule, false */ otherwise
    boolean evaluate(Facts facts);

    /** * Change the method to encapsulate the rule actions *@throwsAn Exception */ is thrown if an error occurs during execution
    void execute(Facts facts) throws Exception;

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

}
Copy the code

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

The execute method encapsulates the actions that should be performed if the rule conditions are met. ConditionandAction interface representation.

Rules can be defined in two different ways:

  • Define it declaratively by adding comments to 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 you want.

@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(a) throws Exception {
        //my final actions}}Copy the code

The @condition annotation marks the method for calculating rule conditions. This method must be public, can have one or more arguments annotated with @fact, and return a Boolean type. Only one method can be annotated with @condition.

The @action annotation marks the method to perform the rule Action. Rules can have more than one operation. You can use the order attribute to perform the actions in the specified order. By default, the order of operations is 0.

2. Define rules using the RuleBuilder API

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

In this example, the Condition instance Condition, and the Action instances are Action1 and Action2.

Define the fact

The Facts API is an abstraction of a set of Facts that are checked for rules. Internally, Facts instances hold HashMap<String, Object>, which means:

  • Facts need to be named. They should have a unique name and cannot be empty
  • Any Java object can act as a fact

Here is an instance defining fact:

Facts facts = new Facts();
facts.add("rain".true);
Copy the code

Facts can be injected with rule conditions, and the action method uses the @fact annotation. In the following rule, the rain fact is injected into the rain parameter of the 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}}Copy the code

The Facts type parameter is injected into known Facts (like the action method takeAnUmbrella).

If an injected FACT is missing, the engine throws a RuntimeException.

Defining a rule engine

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

  • DefaultRulesEngine: Rules are applied according to their natural order (priority by default).
  • InferenceRulesEngine: Continue applying rules to known facts until the rules are no longer applied.

Create a rules engine

To create a rules engine, use the constructor for each implementation:

RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();
Copy the code

You can then trigger the registration rule as follows:

rulesEngine.fire(rules, facts);
Copy the code

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
Copy the code
  • SkipOnFirstAppliedRule: Tells the engine to skip subsequent rules when a rule is triggered.
  • SkipOnFirstFailedRule: Tells the engine to skip subsequent rules if they fail.
  • SkipOnFirstNonTriggeredRule: tell a rule engine will not be triggered to skip the back of the rules.
  • RulePriorityThreshold: Tells the engine to skip the next rule if the priority exceeds the defined threshold. Version 3.3 is not supported. The default value is MaxInt.

These parameters can be specified using the RulesEngineParameters API:

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

Copy the code

To get parameters from the engine, use the following code snippet:

RulesEngineParameters parameters = myEngine.getParameters();
Copy the code

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

Hello world example

We will create a rule that always fires, printing “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(a) {
        return true;
    }

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

Now, let’s create a rule engine and fire 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 = newDefaultRulesEngine(); rulesEngine.fire(rules, facts); }}Copy the code

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
Copy the code

Ok, we’re done.