SpringBoot integrated rule engine Drools

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 the drools module. We can use the KieServive singleton instance to create a KieBuilder instance.
  • Finally, use KieService to create a KieContainer and configure it as a spring 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 /src/main/resources/rules directory -discount.drl file, in which the corresponding rules are defined.

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 the mvel syntax to specify rules. Additionally, each rule is described using the rule 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 a KieSession 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 the drl file.
  • Call the fireAllRules() method to fire all rules.
  • Finally, terminate the session by calling the dispose() method of KieSession.

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 between rule and when, 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 the LHS is empty, it will be treated as a conditional element that is always true.
    You can also define multiple pattern, you can use and or or to connect between multiple pattern, you can also Do not write, the default connection is and
  • 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 is java. Of course, we also recommend using java dialect to reduce maintenance costs.
  • dialect: The attribute is only used to set the grammar of RHS?, and the LHS part is not affected by The 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 use getter and setter methods.
  • For POJO objects, mvel dialect can directly use attribute names for reading and writing, even private 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