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.
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 RulesTechnology 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 UI2. 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 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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
