Article directory
- 1 Integrating rule engine Drools
-
- 1.1 Preface
- 1.2 pom.xml
- 1.3 Drools configuration class
- 1.4 Example Demo
-
- 1.4.1 Add business model
- 1.4.2 Define drools rules
- 1.4.3 Add Service layer
- 1.4.4 Add Controller
- 1.4.5 Testing
- 1.5 Analysis of drools rules
-
- 1.5.1 Introduction
- 1.5.2 Grammatical structure of rule body
- 1.5.3 Notes
- 1.5.4 Pattern pattern matching
- 1.5.5 Comparison Operators
- 1.5.6 dialect attribute
1 Integrated rule engine Drools
1.1 Preface
If there is such a demand, online shopping needs to calculate product discounts according to different rules, such as a 5% discount for VIP customers, a 10% discount for purchases exceeding 1,000 yuan, etc., and these rules may change at any time, or even increase new rules. Faced with this demand, how do you realize it? Could it be that when the calculation rules change, the business code must be modified, retested, and launched.
In fact, we can implement it through a rule engine. Drools
is an open source business rule engine that can be easily integrated with springboot
applications. This article uses Let's use Drools
to realize the requirements mentioned above.
1.2 pom.xml
We create a spring boot application and add drools-related dependencies to the pom, as follows:
<dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>7.59.0.Final</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>7.59.0.Final</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-decisiontables</artifactId> <version>7.59.0.Final</version> </dependency>
1.3 Drools configuration class
Create a configuration java class called DroolsConfig
.
@Configuration public class DroolsConfig {<!-- --> // specify the path to the rule file private static final String RULES_CUSTOMER_RULES_DRL = "rules/customer-discount.drl"; private static final KieServices kieServices = KieServices.Factory.get(); @Bean public KieContainer kieContainer() {<!-- --> KieFileSystem kieFileSystem = kieServices. newKieFileSystem(); kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL)); KieBuilder kb = kieServices. newKieBuilder(kieFileSystem); kb.buildAll(); KieModule kieModule = kb. getKieModule(); KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId()); return kieContainer; } }
Parsing instructions:
- Define a
KieContainer
Spring
Bean
,KieContainer
is used to load the application’s/resources
folder to build the rule engine. - Creates a
KieFileSystem
instance and configures the rules engine and loads the rules’DRL
files from the application’s resource directory. - Use the
KieBuilder
instance to build thedrools
module. We can use theKieServive
singleton instance to create aKieBuilder
instance. - Finally, use
KieService
to create aKieContainer
and configure it as aspring bean
.
1.4 Example Demo
1.4.1 Add business model
Create an order object OrderRequest
, the fields in this class are sent back as input information to the defined drools rules to calculate the discount amount for a given customer order.
@Data public class OrderRequest {<!-- --> /** * client number */ private String customerNumber; /** * age */ private Integer age; /** * order amount */ private Integer amount; /** * Customer type */ private CustomerType customerType; }
Define an enumeration of CustomerType, based on which the rule engine will calculate the customer order discount percentage, as shown below.
public enum CustomerType {<!-- --> LOYAL, NEW, DISSATISFIED; public String getValue() {<!-- --> return this.toString(); } }
Finally, create an order discount class OrderDiscount
to represent the calculated final discount, as shown below.
@Data public class OrderDiscount {<!-- --> /** * Discount */ private Integer discount = 0; }
We will return the calculated discount using the above response object
1.4.2 Define drools rules
The previous DroolsConfig
class specifies the drools
rule directory, now we add customer in the
file, in which the corresponding rules are defined./src/main/resources/rules
directory -discount.drl
The complete rule source code is as follows:
import com.alvin.drools.model.OrderRequest; import com.alvin.drools.model.CustomerType; global com.alvin.drools.model.OrderDiscount orderDiscount; dialect "mvel" // Rule 1: Judging by age rule "Age based discount" when // When the customer's age is under 20 or over 50 OrderRequest(age < 20 || age > 50) then // add 10% discount System.out.println("===========Adding 10% discount for Kids/ senior customer============="); orderDiscount.setDiscount(orderDiscount.getDiscount() + 10); end // Rule 2: Rules based on customer type rule "Customer type based discount - Loyal customer" when // When the customer type is LOYAL OrderRequest(customerType. getValue == "LOYAL") then // add 5% discount System.out.println("==========Adding 5% discount for LOYAL customer============="); orderDiscount.setDiscount(orderDiscount.getDiscount() + 5); end rule "Customer type based discount - others" when OrderRequest(customerType. getValue != "LOYAL") then System.out.println("===========Adding 3% discount for NEW or DISSATISFIED customer============="); orderDiscount.setDiscount(orderDiscount.getDiscount() + 3); end rule "Amount based discount" when OrderRequest(amount > 1000L) then System.out.println("===========Adding 5% discount for amount more than 1000$============="); orderDiscount.setDiscount(orderDiscount.getDiscount() + 5); end
Parsing instructions:
- We use a global parameter called
orderDiscount
that can be shared between multiple rules. - A
drl
file can contain one or more rules. We can use themvel
syntax to specify rules. Additionally, each rule is described using therule
keyword. - Each rule uses
when-then
syntax to define the conditions of the rule. - Based on the input value of the order request, we are adding a discount to the result. Each rule adds an additional discount to the global result variable if the rule expression matches
1.4.3 Add Service layer
Create a service class called OrderDiscountService as follows: .
@Service public class OrderDiscountService {<!-- --> @Autowired private KieContainer kieContainer; public OrderDiscount getDiscount(OrderRequest orderRequest) {<!-- --> OrderDiscount orderDiscount = new OrderDiscount(); // start the session KieSession kieSession = kieContainer. newKieSession(); // set discount object kieSession.setGlobal("orderDiscount", orderDiscount); // set the order object kieSession.insert(orderRequest); // trigger rule kieSession.fireAllRules(); // or through the rule filter to implement only the specified rules //kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("Age based discount")); // abort the session kieSession.dispose(); return order Discount; } }
Parsing instructions:
- Injects a
KieContainer
instance and creates aKieSession
instance. - A global parameter of type
OrderDiscount
is set, which will save the rule execution result. - Use the
insert()
method to pass the request object to thedrl
file. - Call the
fireAllRules()
method to fire all rules. - Finally, terminate the session by calling the
dispose()
method ofKieSession
.
1.4.4 Add Controller
Create a Controller class named OrderDiscountController, the specific code is as follows:
@RestController public class OrderDiscountController {<!-- --> @Autowired private OrderDiscountService orderDiscountService; @PostMapping("/get-discount") public ResponseEntity<OrderDiscount> getDiscount(@RequestBody OrderRequest orderRequest) {<!-- --> OrderDiscount discount = orderDiscountService. getDiscount(orderRequest); return new ResponseEntity<>(discount, HttpStatus.OK); } }
1.4.5 Testing
For LOYAL customer type with age < 20 and amount > 1000, we should get 20% discount according to the rules we defined.
Input parameters: {<!-- --> "customerNumber": "123456", "age": 20, "amount": 20000, "customerType": "LOYAL" } Out of parameters: {<!-- --> "discount": 10 }
Reference link: https://mp.weixin.qq.com/s/SfMhb34dj7DrLvMCKZv9Uw
1.5 Analysis of drools rules
1.5.1 Introduction
A very important job when using Drools
is to write the rule file, usually the suffix of the rule file is .drl
, drl
is Drools Abbreviation for Rule Language
. Write the specific rule content in the rule file.
The content of a complete set of rule files is as follows:
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 |
The rule files supported by Drools, in addition to the drl format, also have the Excel file type.
1.5.2 Grammatical structure of rule bodies
The rule body is an important part of the content of the rule file, and it is the part that judges the business rules and processes the business results.
The grammatical structure of the rule body is as follows:
rule "ruleName" attributes when LHS then RHS end
Parsing instructions:
rule
: keyword, indicating the beginning of the rule, and the parameter is the unique name of the rule.attributes
: Rule attributes, which are parameters betweenrule
andwhen
, are optional.when
: Keyword followed by the condition 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 theLHS
is empty, it will be treated as a conditional element that is alwaystrue
.
You can also define multiplepattern
, you can useand
oror
to connect between multiplepattern
, you can also Do not write, the default connection isand
then
: Keyword followed by the result part of the rule.RHS(Right Hand Side)
: is the generic name for the consequence or action part of the rule.end
: keyword, indicating the end of a rule
1.5.3 Comments
Comments used in rule files in the form of drl
are consistent with comments used in Java classes, and are divided into single-line comments and multi-line comments.
Single-line comments are marked with //
, multi-line comments start with /
and end with /
. For example:
//Comment for rule1, this is a single-line comment rule "rule1" when then System.out.println("rule1 trigger"); end ? /* Annotation for rule rule2, This is a multiline comment */ rule "rule2" when then System.out.println("rule2 trigger"); end
1.5.4 Pattern pattern matching
We already know that the matcher in Drools
can pattern-match all the rules in Rule Base
with the Fact object in Working Memory, then we need to specify in the rule body The LHS section defines rules and does pattern matching. The LHS part consists of one or more conditions, which are also called patterns.
The grammatical structure of
pattern
is: bind variable name:
Object(Field constraint)
Among them, The name of the binding variable can be omitted
, and it is generally recommended to start the name 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.
//Discount of 20 yuan if the total price of books purchased is between 100 and 200 yuan rule "book_discount_2" when //Order is a type constraint, originalPrice is an attribute constraint $order:Order(originalPrice < 200 & amp; & amp; originalPrice >= 100) then $order.setRealPrice($order.getOriginalPrice() - 20); System.out.println("Successfully matched to rule 2: 20 yuan discount for the total price of purchased books between 100 and 200 yuan"); end
//Rule 2: A discount of 20 yuan if the total price of books purchased is between 100 and 200 yuan rule "book_discount_2" when $order:Order($op:originalPrice < 200 & amp; & amp; originalPrice >= 100) then System.out.println("$op=" + $op); $order.setRealPrice($order.getOriginalPrice() - 20); System.out.println("Successfully matched to rule 2: 20 yuan discount for the total price of purchased books between 100 and 200 yuan"); end
The LHS part can also define multiple patterns. Multiple patterns can be connected using and or or, or not written. The default connection is and
//Rule 2: A discount of 20 yuan if the total price of books purchased is between 100 and 200 yuan rule "book_discount_2" when $order:Order($op:originalPrice < 200 & amp; & amp; originalPrice >= 100) and $customer:Customer(age > 20 & amp; & amp; gender=='male') then System.out.println("$op=" + $op); $order.setRealPrice($order.getOriginalPrice() - 20); System.out.println("Successfully matched to rule 2: 20 yuan discount for the total price of purchased books between 100 and 200 yuan"); end
1.5.5 Comparison operators
The comparison operators provided by Drools are: >, <, >=, <=, ==, !=, contains, not contains, memberOf, not memberOf, matches, not matches
The first 6 comparison operators are exactly the same as those in Java. Next, we will focus on the last 6 comparison operators.
contains | not contains
syntax structure
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] not contains value)memberOf | not memberOf
syntax structure
Object(field memberOf value[Collection/Array])
Object(field not memberOf value[Collection/Array])matches | not matches
syntax structure
Object(field matches "Regular Expression")
Object(field not matches "Regular Expression")
1.5.6 dialect attribute
drools
supports two kinds of dialect
: java
? and mvel
:
dialect
: The default isjava
. Of course, we also recommend using java dialect to reduce maintenance costs.dialect
: The attribute is only used to set the grammar ofRHS
?, and theLHS
part is not affected byThe effect of dialect
.
Both package and rule can specify the dialect attribute.
mvel
is an expression language, the github homepage is https://github.com/mvel/mvel? , the document homepage is http://mvel.documentnode.com/
mvel dialect
in dools
can be considered as a superset of java dialect
, that is to say, in mvel dialect
mode, It also supports the writing method of java dialect
The main difference between mvel
and java
:
- For
POJO
objects,java dialect
must usegetter
andsetter
methods. - For
POJO
objects,mvel dialect
can directly use attribute names for reading and writing, evenprivate
attributes are also available.
java dialect example:
rule "java_rule" enabled true dialect "java" when $order:Order() then System.out.println("java_rule fired"); $order.setRealPrice($order.getOriginalPrice()*0.8); end
Example of mvel dialect:
rule "mvel_rule" enabled false dialect "mvel" when $order:Order() then System.out.println("mvel_rule fired"); $order.realPrice=$order.originalPrice*0.7; end