Overview of the rule engine
The main idea of the rule engine is to separate the business decision-making part of the application, and use the predefined semantic template to write business decisions (business rules), which are configured and managed by users or developers when needed.
Welcome to pay attention to the personal public account [Have a good time learning technology] exchange and study
Usage scenario
For example, when shopping in a mall, you can get 100 off when you spend over 300, 200 if you spend over 500, etc., and these rules may change at any time. If this requirement is realized, what should we do under normal circumstances?
if…else
pseudocode
if(amount >= 300) {<!-- --> amount -= 100; } else if(amount >= 500) {<!-- --> amount -= 200; }
Strategy pattern
pseudocode
interface Strategy {<!-- --> reductionAmount(int amount); } class Strategy1 {<!-- --> reductionAmount(int amount); } class Strategy2 {<!-- --> reductionAmount(int amount); }
The above methods can realize the function, but what if the discount changes frequently? Do different products have different rules? Moreover, these rules are heavily coupled with the code, and each rule change requires retesting and deployment.
Introduction to Drools
drools is an open source rule engine developed by the JBoss organization based on the java language. It can liberate complex and changeable business rules from hard coding and store them in files or specific storage media in the form of rule scripts (such as Stored in the database), so that changes in business rules can take effect immediately in the online environment without modifying the project code or restarting the server.
drools official website: https://www.drools.org/
drools Chinese website: Drools Chinese website | A powerful open source rule engine based on java
Drools source code download address: https://github.com/kiegroup/drools
springboot integrated drools case
Business scenario
order amount | discount amount |
---|---|
100-200 | 10 |
200-500 | 20 |
500-1000 | 50 |
1000-2000 | 100 |
2000-5000 | 300 |
5000-10000 | 500 |
more than 10000 | 1000 |
Create a springboot project
Create a maven project and import drools related dependencies
<!-- https://mvnrepository.com/artifact/org.drools/drools-compiler --> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>7.73.0.Final</version> </dependency> <!-- https://mvnrepository.com/artifact/org.drools/drools-mvel --> <dependency> <groupId>org.drools</groupId> <artifactId>drools-mvel</artifactId> <version>7.73.0.Final</version> </dependency> <!-- test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
resources/META-INF/kmodule.xml
<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"> <!-- name: Specify the name of kbase, which can be arbitrary, but needs to be unique packages: Specifies the directory of the rule file, which needs to be filled in according to the actual situation, otherwise it cannot be loaded into the rule file default: Specifies whether the current kbase is the default --> <kbase name="fdf" packages="com.fandf.rules" default="true"> <!-- name: Specify the name of ksession, which can be arbitrary, but needs to be unique default: Specifies whether the current session is the default --> <ksession name="fdf-rule" default="true"/> </kbase> </kmodule>
Create entity order
package com.fandf.test.entity; import lombok.Data; import java.math.BigDecimal; /** * @author fandongfeng * @date 2023/5/3 19:17 */ @Data public class Order {<!-- --> /** * Price before order discount */ private BigDecimal originalPrice; /** * Price after order discount */ private BigDecimal realPrice; }
Create a rule file resources/rules/orderDiscount.drl
// Order discount rules must be consistent with the package of kmodule.xml package com.fandf.rules import com.fandf.test.entity.Order import java.math.BigDecimal // Rule 1: 100-200 discount 10 rule "order_discount_1" when $order: Order(originalPrice >= 100 & amp; & amp; originalPrice < 200) // match the pattern, find the Order object in the rule engine (working memory), and name it $order then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(10))); System.out.println("Rule 1 is successfully matched, and the order amount is discounted by 10 yuan"); end // Rule 2: 200-500 discount 20 rule "order_discount_2" when $order: Order(originalPrice >= 200 & amp; & amp; originalPrice < 500) then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(20))); System.out.println("Rule 2 is successfully matched, and the order amount is discounted by 20 yuan"); end // Rule 3: 500-1000 discount 50 rule "order_discount_3" when $order: Order(originalPrice >= 500 & amp; & amp; originalPrice < 1000) then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(50))); System.out.println("Rule 3 is successfully matched, and the order amount is discounted by 50 yuan"); end // Rule 4: 1000-2000 discount 100 rule "order_discount_4" when $order: Order(originalPrice >= 1000 & amp; & amp; originalPrice < 2000) then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(100))); System.out.println("Rule 4 is successfully matched, and the order amount is discounted by 100 yuan"); end // Rule five: 2000-5000 discount 300 rule "order_discount_5" when $order: Order(originalPrice >= 2000 & amp; & amp; originalPrice < 5000) then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(300))); System.out.println("Rule 5 is successfully matched, and the order amount is discounted by 300 yuan"); end // Rule 6: 5000-10000 discount 500 rule "order_discount_6" when $order: Order(originalPrice >= 5000 & amp; & amp; originalPrice < 10000) then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(500))); System.out.println("Rule 6 is successfully matched, and the order amount is discounted by 500 yuan"); end // Rule 7: 1000 discount for more than 10000 rule "order_discount_7" when $order: Order(originalPrice >= 10000) then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(1000))); System.out.println("Rule 7 is successfully matched, and the order amount is discounted by 1,000 yuan"); end
Write a test class OrderTest
package com.fandf.test.entity; import com.fandf.test.Application; import org.junit.jupiter.api.Test; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.springframework.boot.test.context.SpringBootTest; import java.math.BigDecimal; @SpringBootTest(classes = Application. class) public class OrderTest {<!-- --> @Test public void test(){<!-- --> KieServices kieServices = KieServices.Factory.get(); // Get the Kie container object default container object KieContainer kieContainer = kieServices. newKieClasspathContainer(); // Get the session object from the Kie container object (the default session object KieSession kieSession = kieContainer. newKieSession(); Order order = new Order(); order.setOriginalPrice(BigDecimal.valueOf(180)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); kieSession = kieContainer. newKieSession(); order.setOriginalPrice(BigDecimal.valueOf(300)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); kieSession = kieContainer. newKieSession(); order.setOriginalPrice(BigDecimal.valueOf(600)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); kieSession = kieContainer. newKieSession(); order.setOriginalPrice(BigDecimal.valueOf(1200)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); kieSession = kieContainer. newKieSession(); order.setOriginalPrice(BigDecimal.valueOf(3000)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); kieSession = kieContainer. newKieSession(); order.setOriginalPrice(BigDecimal.valueOf(8000)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); kieSession = kieContainer. newKieSession(); order.setOriginalPrice(BigDecimal.valueOf(12000)); // Insert the order object into the working memory kieSession.insert(order); // match object // Activate the rules, and the drools framework automatically performs rule matching. If the match is successful, execute kieSession.fireAllRules(); System.out.println("Price before discount: " + order.getOriginalPrice() + ", price after discount: " + order.getRealPrice()); // close the session kieSession.dispose(); } }
Execution output
[fdf-test:0.0.0.0:9002] 2023-05-03 22:25:03.823 INFO 5536 [-] [main] o.d.c.k.b.impl.ClasspathKieProject Found kmodule: file:/Users/dongfengfan/IdeaProjects/SpringCloudLearning/ fdf-test/target/classes/META-INF/kmodule.xml [fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.019 WARN 5536 [-] [main] o.d.c.k.b.impl.ClasspathKieProject Unable to find pom.properties in /Users/dongfengfan/IdeaProjects/SpringCloudLearning/fdf -test/target/classes [fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.024 INFO 5536 [-] [main] o.d.c.k.b.impl.ClasspathKieProject Recursed up folders, found and used pom.xml /Users/dongfengfan/IdeaProjects/ Spring Cloud Learning/fdf-test/pom.xml [fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.025 INFO 5536 [-] [main] o.d.c.k.b.i.InternalKieModuleProvider Creating KieModule for artifact com.fandf:fdf-test:1.0.0 [fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.035 INFO 5536 [-] [main] o.d.c.kie.builder.impl.KieContainerImpl Start creation of KieBase: fdf [fdf-test:0.0.0.0:9002] 2023-05-03 22:25:04.041 WARN 5536 [-] [main] o.d.c.kie.builder.impl.KieBuilderImpl File 'rules/orderDiscount.drl' is in folder 'rules ' but declares package 'com.fandf.rules'. It is advised to have a correspondence between package and folder names. [fdf-test:0.0.0.0:9002] 2023-05-03 22:25:05.091 INFO 5536 [-] [main] o.d.c.kie.builder.impl.KieContainerImpl End creation of KieBase: fdf Successfully matched to rule 1, the order amount is discounted by 10 yuan Price before discount: 180, price after discount: 170 Successfully matched to rule 2, the order amount is discounted by 20 yuan Price before discount: 300, price after discount: 280 Successfully matched to rule 3, the order amount is discounted by 50 yuan Price before discount: 600, price after discount: 550 Successfully matched to rule 4, the order amount is discounted by 100 yuan Price before discount: 1200, price after discount: 1100 Successfully matched to rule 5, the order amount is discounted by 300 yuan Price before discount: 3000, price after discount: 2700 Successfully matched to rule 6, the order amount is discounted by 500 yuan Price before discount: 8000, price after discount: 7500 Successfully matched to rule 7, the order amount is discounted by 1,000 yuan Price before discount: 12000, price after discount: 11000
Drools syntax
rules file
A very important job when using Drools is to write the rule file, usually the suffix of the rule file is .drl.
drl is the abbreviation of Drools Rule Language. Write the specific rule content in the rule file.
keyword | description |
---|---|
package | Package name, only limited to logical management, queries or functions under the same package name can be called directly |
import | used to import classes or Static method |
global | global variable |
function | custom Function |
query | query |
rule end | rule body |
Rule Body Grammar
The rule body is the part that judges business rules and processes business results.
rule "ruleName" attributes when LHS then RHS end
rule: keyword, indicating the beginning of the rule, and the parameter is the unique name of the rule.
attribute: rule attribute, which is a parameter between rule and when, and is optional.
when: Keyword followed by the conditional part of the rule.
LHS (Left Hand Side): is the generic name for the condition part of the rule. It consists of zero or more conditional elements. If the LHS is empty, it is treated as a conditional element that is always true.
then: Keyword followed by the result part of the rule.
RHS (Right Hand Side): is the general name for the consequence or action part of the rule.
end: keyword, indicating the end of a rule.
Pattern pattern matching
The grammatical structure of pattern is: bind variable name: Object (Field constraint)
Among them, the binding variable name can be omitted, and it is generally recommended to start the naming of the binding variable name with $. If a bind variable name is defined, it can be used in the RHS part of the rule body to manipulate the corresponding Fact object. The Field constraint part is zero or more expressions that need to return true or false.
such as our case
rule "order_discount_1" when $order: Order(originalPrice >= 100 & amp; & amp; originalPrice < 200) // match the pattern, find the Order object in the rule engine (working memory), and name it $order then $order.setRealPrice($order.getOriginalPrice().subtract(BigDecimal.valueOf(10))); System.out.println("Rule 1 is successfully matched, and the order amount is discounted by 10 yuan"); end
symbol | description | |
---|---|---|
greater than | ||
less than | ||
>= | greater than or equal to | |
<= | less than or equal to | |
=== | equal to | td> |
!= | is not equal to | |
contains | check a Fact object Whether an attribute value contains a specified object value | |
not contains | Check whether an attribute value of a Fact object does not contain a specified object Value | |
memberOf | Determines whether a property of a Fact object is in one or more collections | |
not memberOf | Judge whether a property of a Fact object is not in one or more collections | |
matches | Determine whether the properties of a Fact object match the provided standard Java regular expressions | |
not matches | Determine whether the properties of a Fact object do not match Provided standard Java regular expressions for matching |