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.