Backend Development 9 min read

Decoupling Complex Business Logic with a Custom Rule Engine

This article explains how a lightweight, Groovy‑based rule engine can decouple complex ticketing business logic from core systems, outlines its architecture, implementation details, performance optimizations, and provides code examples and benchmark results.

Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Decoupling Complex Business Logic with a Custom Rule Engine

Complex business logic in ticketing systems leads to high maintenance cost; a rule engine is proposed to decouple logic.

The rule engine works in three steps: accept data, interpret rules, make decisions, allowing separation of rules from the core system.

Typical use cases include risk control, real‑time recommendation, lightweight offline calculations, and any scenario requiring decoupled complex logic.

Existing open‑source engines (Drools) and commercial solutions (ILOG JRules) were evaluated; due to steep learning curves and poor fit, a lightweight custom engine was built.

Key implementation goals: define rule language semantics (Groovy), dynamic rule condition editing, script editing, cluster‑wide publishing, and automated execution.

Challenges include rule priority, conflict avoidance, and designing a comprehensive rule‑management model.

Architecture: rules are stored with metadata (name, code, description, content, priority, status) and executed by loading Groovy scripts via GroovyClassLoader, caching them in ConcurrentHashMap, and invoking them with a Binding.

Performance optimizations: cache Script objects, use separate GroovyClassLoader per script, avoid frequent class generation that causes GC pressure and OOM.

Example code for rule initialization, caching, and execution is shown below.

public GroovyRuleScript(@NonNull RuleScriptItemDTO ruleScriptItem) { this.createTime = System.currentTimeMillis(); GroovyClassLoader classLoader = new GroovyClassLoader(); Class
clazz = classLoader.parseClass(ruleScriptItem.getScript()); this.scriptClass = clazz; this.scriptId = ruleScriptItem.getScriptId(); this.name = ruleScriptItem.getName(); classLoader = null; }
ConcurrentHashMap
ruleScriptMaps = new ConcurrentHashMap<>(16); ConcurrentHashMap
shuntScriptMaps = new ConcurrentHashMap<>(16); ConcurrentHashMap
postScriptMaps = new ConcurrentHashMap<>(16); for (RuleScriptItemDTO scriptItem : scriptResponse.getData()) { try { if (Constants.RULE_TYPE.equals(scriptItem.getType())) { ruleScriptMaps.put(scriptItem.getScriptId(), new GroovyRuleScript(scriptItem)); } else if (Constants.SHUNT_TYPE.equals(scriptItem.getType())) { shuntScriptMaps.put(scriptItem.getScriptId(), new GroovyRuleScript(scriptItem)); } else if (Constants.POST_TYPE.equals(scriptItem.getType())) { postScriptMaps.put(scriptItem.getScriptId(), new GroovyRuleScript(scriptItem)); } } catch (Exception e) { log.error("[RuleStrategyRepository] init script error:{}", e.getMessage(), e); } } ruleScriptPool.reload(ruleScriptMaps); shuntScriptPool.reload(shuntScriptMaps); postScriptPool.reload(postScriptMaps);
Object execute(Binding binding) throws ExecuteScriptException;

Benchmarks show peak TPS of 29,900, with 99.9% of rule executions completing within 10 ms, 99.99% within 50 ms, and 99.999% within 500 ms.

The article concludes with UML class and sequence diagrams illustrating the engine.

backendJavaperformancerule-enginebusiness-logicGroovy
Tongcheng Travel Technology Center
Written by

Tongcheng Travel Technology Center

Pursue excellence, start again with Tongcheng! More technical insights to help you along your journey and make development enjoyable.

0 followers
Reader feedback

How this landed with the community

login 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.