Using Spring Expression Language (SpEL) to Create a Simple Rule Engine

Using Spring Expression Language (SpEL) to Create a Simple Rule Engine

Spring Expression Language (SpEL) is a powerful feature that enables dynamic querying and manipulation of objects at runtime. In this article, we will explore how to leverage SpEL in a Spring Boot 3 application to define and evaluate business rules derived from records stored in a database. We will focus on an example involving discount rules for an e-commerce application.

Overview of SpEL

SpEL allows developers to execute expressions on objects, making it particularly useful for creating flexible business rules that can change based on data in your database.

Use Case

Imagine an e-commerce application where you want to apply discounts based on criteria such as customer age and membership level. These criteria will be stored in a database, and SpEL will evaluate them dynamically to determine applicable discounts.

Setting Up the Spring Boot 3 Project

1. Create a Spring Boot Project

You can set up a new Spring Boot project using Spring Initializr. Include the following dependencies:

  • Spring Web
  • Spring Data JPA
  • H2 Database (for simplicity)

2. Add Dependencies in pom.xml

Here’s an example of what your pom.xml might look like for Spring Boot 3:

<project xmlns="https://maven.apache.org/POM/4.0.0"
         xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>discount-rules</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>discount-rules</name>
    <description>Demo project for Spring Boot 3</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>        

Defining the Database Entity

Create an entity class for the discount rules.

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;

@Entity
public class DiscountRule {
    @Id
    @GeneratedValue
    private Long id;
    private String condition; // SpEL condition
    private double discountPercentage;

    // Constructors
    public DiscountRule() {}

    public DiscountRule(String condition, double discountPercentage) {
        this.condition = condition;
        this.discountPercentage = discountPercentage;
    }

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCondition() {
        return condition;
    }

    public void setCondition(String condition) {
        this.condition = condition;
    }

    public double getDiscountPercentage() {
        return discountPercentage;
    }

    public void setDiscountPercentage(double discountPercentage) {
        this.discountPercentage = discountPercentage;
    }
}        

Creating the Repository

Now, create a Spring Data JPA repository to manage the DiscountRule entities.

import org.springframework.data.jpa.repository.JpaRepository;

public interface DiscountRuleRepository extends JpaRepository<DiscountRule, Long> {
}        

Sample Data in Database

Here are some example discount rules that you might store in the database:

Populate the Database

You can use the CommandLineRunner to populate the database with these rules when the application starts.

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
public class DataInitializer implements CommandLineRunner {

    private final DiscountRuleRepository repository;

    public DataInitializer(DiscountRuleRepository repository) {
        this.repository = repository;
    }

    @Override
    public void run(String... args) throws Exception {
        repository.saveAll(Arrays.asList(
            new DiscountRule("customerAge > 60", 10),
            new DiscountRule("membershipLevel == 'GOLD'", 15),
            new DiscountRule("membershipLevel == 'SILVER'", 5),
            new DiscountRule("totalAmount > 100", 20)
        ));
    }
}        

Evaluating Rules with SpEL

Create a service that retrieves the discount rules and evaluates them based on the current order.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DiscountService {

    @Autowired
    private DiscountRuleRepository discountRuleRepository;

    public double calculateDiscount(Order order) {
        List<DiscountRule> rules = discountRuleRepository.findAll();
        double totalDiscount = 0;

        ExpressionParser parser = new SpelExpressionParser();
        for (DiscountRule rule : rules) {
            StandardEvaluationContext context = new StandardEvaluationContext(order);
            boolean isMatch = parser.parseExpression(rule.getCondition()).getValue(context, Boolean.class);
            if (isMatch) {
                totalDiscount += order.getTotalAmount() * (rule.getDiscountPercentage() / 100);
            }
        }
        return totalDiscount;
    }
}        

Example Order Class

Here’s a simple Order class representing an order in your application.

public class Order {
    private double totalAmount;
    private int customerAge;
    private String membershipLevel;

    // Getters and Setters
    public double getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(double totalAmount) {
        this.totalAmount = totalAmount;
    }

    public int getCustomerAge() {
        return customerAge;
    }

    public void setCustomerAge(int customerAge) {
        this.customerAge = customerAge;
    }

    public String getMembershipLevel() {
        return membershipLevel;
    }

    public void setMembershipLevel(String membershipLevel) {
        this.membershipLevel = membershipLevel;
    }
}        

Creating a REST Controller

Create a REST controller to expose an endpoint for calculating discounts based on the order.

Example Request

To test the application, you can use a tool like Postman or curl. Here’s an example of how to send a request:

Request :

POST /api/discount
Content-Type: application/json

{
    "totalAmount": 150,
    "customerAge": 65,
    "membershipLevel": "GOLD"
}        

Expected Response

Given the rules in the database, the expected response would be:

  • Discount for age > 60: 10% of 150 = 15.00
  • Discount for GOLD membership: 15% of 150 = 22.50
  • Discount for totalAmount > 100: 20% of 150 = 30.00

Total discount = 15.00 + 22.50 + 30.00 = 67.50.

Conclusion

By leveraging Spring Expression Language (SpEL) in a Spring Boot 3 application, you can create dynamic and flexible business rules evaluated at runtime based on data stored in your database. This approach allows for greater adaptability in business logic, making it easier to implement complex rules without hardcoding them into your application.

Further Reading

With this setup, your e-commerce application can dynamically apply discounts based on changing business rules, ensuring it remains responsive to customer needs.

要查看或添加评论,请登录

kiarash shamaei的更多文章

社区洞察

其他会员也浏览了