Boost Business Agility with a Spring Boot Dynamic Rule Engine

This article explains how backend developers can replace hard‑coded business logic with a dynamic rule engine built on Spring Boot and QLExpress, enabling rapid rule changes, reducing deployment risk, and empowering non‑technical staff to manage complex e‑commerce, finance, and marketing scenarios.

macrozheng
macrozheng
macrozheng
Boost Business Agility with a Spring Boot Dynamic Rule Engine

Introduction

As a backend developer you often need to change business rules quickly: operations, product managers, and executives request rule adjustments that traditionally require code changes, rebuilding, redeployment, and service restarts, causing delays and risk.

Business Pain Points

Traditional Approach

In traditional systems business rules are hard‑coded in the code base.

// Traditional hard‑coded method
public BigDecimal calculateVipDiscount(String userLevel, BigDecimal price) {
    if ("GOLD".equals(userLevel)) {
        return price.multiply(new BigDecimal("0.8")); // 20% off
    } else if ("SILVER".equals(userLevel)) {
        return price.multiply(new BigDecimal("0.9")); // 10% off
    }
    return price; // no discount
}

Slow response : rule changes require a full development cycle.

High risk : each deployment may affect the entire system.

High cost : coordination among development, testing, and operations.

Inflexible : cannot react quickly to market changes.

Real‑world Scenarios

E‑commerce : frequent promotion adjustments, A/B testing, competitor price changes.

Finance : real‑time risk model tuning, interest‑rate policy updates, audit rule optimization.

Content platforms : recommendation algorithm tweaks, content moderation rule updates, user‑level adjustments.

Solution: Dynamic Rule Engine

Core Idea

The engine separates business rules from business code, storing rules as scripts that are executed at runtime.

Business code : handles workflow and data processing.

Business rules : stored as independent scripts, executed dynamically.

Traditional: business logic = workflow + hard‑coded rules
Dynamic:    business logic = workflow + scripted rules

Technology Stack

Rule engine: QLExpress (Alibaba open‑source)

Backend framework: Spring Boot

Frontend: HTML5 + TailwindCSS

Core Implementation

1. Project Structure

springboot-dynamic-rule/
├── entity/
│   ├── RuleScript.java          // rule entity
│   ├── OrderProcessResult.java   // order result
│   └── RuleExecuteResponse.java // rule execution response
├── service/
│   ├── DynamicRuleEngine.java    // rule engine core
│   └── OrderService.java         // business service
├── controller/
│   ├── RuleController.java      // rule management API
│   └── OrderController.java     // business API
└── resources/
    └── static/
        ├── index.html          // rule management page
        └── business.html       // business demo page

2. Rule Engine Core

@Slf4j
@Service
public class DynamicRuleEngine {
    private final Map<String, RuleScript> ruleCache = new ConcurrentHashMap<>();
    private final ExpressRunner expressRunner = new ExpressRunner();

    @PostConstruct
    public void init() { initDefaultRules(); }

    private void initDefaultRules() {
        // VIP discount rule
        addRule(new RuleScript("vip_discount",
            "if (userLevel == \"GOLD\") { return price * 0.8; } " +
            "else if (userLevel == \"SILVER\") { return price * 0.9; } " +
            "else { return price; }",
            "VIP discount rule"));
        // Full‑reduction rule
        addRule(new RuleScript("full_reduction",
            "if (totalAmount >= 200) { return totalAmount - 50; } " +
            "else if (totalAmount >= 100) { return totalAmount - 20; } " +
            "else { return totalAmount; }",
            "Full‑reduction rule"));
    }

    public RuleExecuteResponse executeRule(String ruleName, Map<String, Object> params) {
        try {
            RuleScript rule = ruleCache.get(ruleName);
            if (rule == null || !rule.isEnabled()) {
                return RuleExecuteResponse.error("Rule not found or disabled: " + ruleName);
            }
            DefaultContext<String, Object> context = new DefaultContext<>();
            if (params != null) { params.forEach(context::put); }
            Object result = expressRunner.execute(rule.getScript(), context, null, true, false);
            log.info("Executed rule: {}, result: {}", ruleName, result);
            return RuleExecuteResponse.success(result);
        } catch (Exception e) {
            log.error("Rule execution failed: {}", ruleName, e);
            return RuleExecuteResponse.error("Execution failed: " + e.getMessage());
        }
    }
}

3. Business Service Integration

@Service
@RequiredArgsConstructor
public class OrderService {
    private final DynamicRuleEngine ruleEngine;

    public OrderProcessResult processOrder(Order order) {
        List<OrderProcessResult.ProcessStep> steps = new ArrayList<>();
        BigDecimal currentAmount = order.getOriginalAmount();
        // Apply VIP discount
        BigDecimal discounted = applyVipDiscount(order, currentAmount, steps);
        // Apply full‑reduction
        BigDecimal finalAmount = applyFullReduction(order, discounted, steps);
        // Calculate points (omitted)
        return new OrderProcessResult(/* build result */);
    }

    private BigDecimal applyVipDiscount(Order order, BigDecimal amount, List<OrderProcessResult.ProcessStep> steps) {
        Map<String, Object> params = new HashMap<>();
        params.put("userLevel", order.getUserLevel());
        params.put("price", amount);
        RuleExecuteResponse resp = ruleEngine.executeRule("vip_discount", params);
        if (resp.isSuccess()) {
            BigDecimal result = new BigDecimal(resp.getResult().toString());
            steps.add(new OrderProcessResult.ProcessStep("VIP discount",
                "Applied discount for level " + order.getUserLevel(), amount, result, amount.subtract(result), "vip_discount"));
            return result;
        }
        return amount;
    }
}

4. REST API

@RestController
@RequestMapping("/api/rules")
@RequiredArgsConstructor
public class RuleController {
    private final DynamicRuleEngine dynamicRuleEngine;

    @GetMapping
    public ResponseEntity<List<RuleScript>> getAllRules() {
        return ResponseEntity.ok(dynamicRuleEngine.getAllRules());
    }

    @PostMapping
    public ResponseEntity<String> addRule(@RequestBody RuleScript ruleScript) {
        try {
            dynamicRuleEngine.addRule(ruleScript);
            return ResponseEntity.ok("Rule added successfully");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("Add failed: " + e.getMessage());
        }
    }

    @PutMapping("/{ruleName}")
    public ResponseEntity<String> updateRule(@PathVariable String ruleName, @RequestBody RuleScript ruleScript) {
        try {
            dynamicRuleEngine.updateRule(ruleName, ruleScript);
            return ResponseEntity.ok("Rule updated successfully");
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("Update failed: " + e.getMessage());
        }
    }

    @PostMapping("/execute/{ruleName}")
    public ResponseEntity<RuleExecuteResponse> executeRule(@PathVariable String ruleName, @RequestBody Map<String, Object> params) {
        RuleExecuteResponse resp = dynamicRuleEngine.executeRule(ruleName, params);
        return ResponseEntity.ok(resp);
    }
}

Operation UI

The demo provides a simple UI for rule management and business scenario simulation.

Rule Management Page

Rule list: displays all rules and their status.

Online editing: modify rule scripts directly in the browser.

Real‑time testing: test rule effects immediately after changes.

Status control: enable or disable rules with a single click.

Business Demo Page

Order simulator: input order data and view processing results.

Detailed steps: clearly show the execution process of each rule.

History: record past processing outcomes.

Application Scenarios

Scenario 1: E‑commerce Dynamic Pricing

Requirement: calculate discount based on user level and order amount.

// Hard‑coded version
if ("GOLD".equals(userLevel) && amount >= 100) { return amount * 0.8; }
// Scripted version
if (userLevel == "GOLD" && totalAmount >= 100) { return totalAmount * 0.8; }
else if (userLevel == "SILVER" && totalAmount >= 50) { return totalAmount * 0.9; }
else { return totalAmount; }

Speed: rule changes from hours to minutes.

Risk: from system‑wide deployments to isolated rule updates.

Barrier: business users can modify rules without developers.

Scenario 2: Real‑time Risk Control

Requirement: adjust audit strategy according to live risk metrics.

// Risk scoring script
score = baseScore;
if (userAge < 18) { score -= 20; }
if (creditLevel == "HIGH") { score += 30; }
if (monthlyIncome > 10000) { score += 15; }
return score >= 60 ? "PASS" : "REJECT";

Fast response to market risk changes.

Fine‑grained control for different scenarios.

Supports A/B testing of multiple strategies.

Scenario 3: Marketing Campaign Configuration

Requirement: flexible rules for complex promotions.

// Double‑11 promotion script
if (activityType == "DOUBLE11") {
    if (totalAmount >= 1000) { return totalAmount - 200; }
    else if (totalAmount >= 500) { return totalAmount - 80; }
    else if (totalAmount >= 200) { return totalAmount - 30; }
}
return totalAmount;

Pre‑warm campaigns with scheduled activation.

Real‑time optimization based on performance data.

Instant rollback when issues are detected.

Conclusion

Business agility: rule changes from hours to minutes.

System stability: fewer deployments and lower risk.

Team collaboration: business users can edit rules directly.

Cost reduction: less effort for development, testing, and operations.

Source code: https://github.com/yuboon/java-examples/tree/master/springboot-dynamic-rule

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavamicroservicesSpring BootQLExpressBusiness RulesDynamic Rule Engine
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.