How a Dynamic Rule Engine Can Cut Deployment Time from Hours to Minutes

This article explains how separating business rules from code using a dynamic rule engine—implemented with Spring Boot, QLExpress, and a simple HTML/Tailwind front‑end—enables rapid, low‑risk rule changes for e‑commerce, finance, and content platforms, illustrated with full code examples and real‑world scenarios.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
How a Dynamic Rule Engine Can Cut Deployment Time from Hours to Minutes

Preface

As a backend developer, you may have faced urgent requests to modify discount rules, risk‑control strategies, or VIP pricing, which traditionally require code changes, rebuilding, deployment, and service restarts, often taking half an hour to several hours.

Business Pain Point: Rule‑Change Challenges

Drawbacks of Traditional Approach

In conventional systems, business rules are hard‑coded in the business logic:

// 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
}

Problems include slow response, high release risk, high cost, and lack of flexibility.

Real Business Scenarios

e‑Commerce: frequent promotional rule adjustments, A/B testing, competitor price changes.

Finance: real‑time risk model tweaks, interest rate policy updates, dynamic audit rules.

Content Platforms: algorithm optimization, content moderation rule updates, user‑level adjustments.

Solution: Dynamic Rule Engine

Core Idea

The essence of a dynamic rule engine is to separate business rules from business code . The code handles workflow and data processing, while rules are stored as scripts and executed at runtime.

Traditional: BusinessLogic = Workflow + Hard‑coded Rules
Dynamic:    BusinessLogic = Workflow + Scripted Rules

Technology Stack

Demo uses:

Rule Engine: QLExpress (Alibaba open‑source)

Backend Framework: Spring Boot

Frontend: HTML5 + TailwindCSS

Core Implementation: Step‑by‑Step Build

1. Project Structure

springboot-dynamic-rule/
├── entity/
│   ├── RuleScript.java          # Rule entity
│   ├── OrderProcessResult.java # Order processing result
│   └── RuleExecuteResponse.java# Execution response
├── service/
│   ├── DynamicRuleEngine.java   # Core engine
│   └── OrderService.java       # Business service
├── controller/
│   ├── RuleController.java     # Rule management API
│   └── OrderController.java    # Business API
└── resources/static/
    ├── index.html               # Rule management UI
    └── business.html            # Business demo UI

2. Rule Engine Core

@Slf4j
@Service
public class DynamicRuleEngine {
    // In‑memory rule cache (replace with DB in production)
    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());
            BigDecimal reduction = amount.subtract(result);
            steps.add(new OrderProcessResult.ProcessStep(
                "VIP Discount",
                "Apply discount for level " + order.getUserLevel(),
                amount, result, reduction, "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 simulation, allowing users to list, edit, test, and enable/disable rules in real time.

Rule management UI
Rule management UI
Business demo UI
Business demo UI

Rule Management Page

Main features: rule list with status, online editing, instant testing, one‑click enable/disable.

Business Demo Page

Main features: order simulator, detailed step rendering, processing history.

// Front‑end dynamic rule execution (simplified)
async function executeRule(ruleName, params) {
    try {
        const response = await fetch(`/api/rules/execute/${ruleName}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(params)
        });
        const result = await response.json();
        if (result.success) {
            displayResult(`Execution succeeded! Result: ${result.result}`);
        } else {
            displayResult(`Execution failed: ${result.errorMessage}`);
        }
    } catch (error) {
        displayResult(`Request failed: ${error.message}`);
    }
}

Practical Scenarios

Scenario 1 – e‑Commerce Dynamic Pricing

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

// Script example
if (userLevel == "GOLD" && totalAmount >= 100) { return totalAmount * 0.8; }
else if (userLevel == "SILVER" && totalAmount >= 50) { return totalAmount * 0.9; }
else { return totalAmount; }

Result: response time drops from hours to minutes, release risk shifts from system‑wide to rule‑level, and business users can modify rules directly.

Scenario 2 – Real‑Time Risk Control

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

Allows instant strategy adjustments, fine‑grained control, and A/B testing.

Scenario 3 – Marketing Activity Configuration

// 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;

Supports pre‑configuration, real‑time tweaks, and rapid rollback.

Conclusion

By extracting business rules from code, we achieve:

Business Agility : rule changes from hours to minutes.

System Stability : fewer deployments and lower risk.

Team Collaboration : business staff can directly edit rules.

Cost Reduction : less development, testing, and operations effort.

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.

BackendBusiness RulesDynamic Rule Engine
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.