Designing a Flexible Java Rule Engine: From Concept to Code
This article walks through a real‑world Java rule‑engine implementation, covering the business requirements, abstract rule design, executor construction, sample code, and a balanced discussion of its advantages and drawbacks for backend developers.
Business Scenario
In a recent internal request, the author needed to extend an existing trial‑user application rule set. The conditions involve filtering overseas users, fraudulent users, unpaid users outside service hours, and allowing referrals, paid, or internally recommended users.
if (是否海外用户) { return false; }
if (刷单用户) { return false; }
if (未付费用户 && 不再服务时段) { return false; }
if (转介绍用户 || 付费用户 || 内推用户) { return true; }The analysis shows the logic relies on AND/OR relationships with short‑circuit evaluation, and while a quick tweak is possible, long‑term maintainability would suffer.
Rule Executor Design
The author proposes a refactored rule executor, presenting a V1 design with code examples.
Rule Abstraction and Implementation
// Business data
@Data
public class RuleDto {
private String address;
private int age;
}
// Rule abstraction
public interface BaseRule {
boolean execute(RuleDto dto);
}
// Abstract rule template
public abstract class AbstractRule implements BaseRule {
protected <T> T convert(RuleDto dto) { return (T) dto; }
@Override
public boolean execute(RuleDto dto) { return executeRule(convert(dto)); }
protected <T> boolean executeRule(T t) { return true; }
}
// Concrete rule example 1
public class AddressRule extends AbstractRule {
@Override
public boolean execute(RuleDto dto) {
System.out.println("AddressRule invoke!");
return dto.getAddress().startsWith(MATCH_ADDRESS_START);
}
}
// Concrete rule example 2
public class NationalityRule extends AbstractRule {
@Override
protected <T> T convert(RuleDto dto) {
NationalityRuleDto nrDto = new NationalityRuleDto();
if (dto.getAddress().startsWith(MATCH_ADDRESS_START)) {
nrDto.setNationality(MATCH_NATIONALITY_START);
}
return (T) nrDto;
}
@Override
protected <T> boolean executeRule(T t) {
System.out.println("NationalityRule invoke!");
NationalityRuleDto nrDto = (NationalityRuleDto) t;
return nrDto.getNationality().startsWith(MATCH_NATIONALITY_START);
}
}
// Constants
public class RuleConstant {
public static final String MATCH_ADDRESS_START = "北京";
public static final String MATCH_NATIONALITY_START = "中国";
}Executor Construction
public class RuleService {
private Map<Integer, List<BaseRule>> hashMap = new HashMap<>();
private static final int AND = 1;
private static final int OR = 0;
public static RuleService create() { return new RuleService(); }
public RuleService and(List<BaseRule> ruleList) { hashMap.put(AND, ruleList); return this; }
public RuleService or(List<BaseRule> ruleList) { hashMap.put(OR, ruleList); return this; }
public boolean execute(RuleDto dto) {
for (Map.Entry<Integer, List<BaseRule>> item : hashMap.entrySet()) {
List<BaseRule> ruleList = item.getValue();
switch (item.getKey()) {
case AND:
System.out.println("execute key = " + 1);
if (!and(dto, ruleList)) return false;
break;
case OR:
System.out.println("execute key = " + 0);
if (!or(dto, ruleList)) return false;
break;
default:
break;
}
}
return true;
}
private boolean and(RuleDto dto, List<BaseRule> ruleList) {
for (BaseRule rule : ruleList) {
if (!rule.execute(dto)) return false;
}
return true;
}
private boolean or(RuleDto dto, List<BaseRule> ruleList) {
for (BaseRule rule : ruleList) {
if (rule.execute(dto)) return true;
}
return false;
}
}Executor Invocation
public class RuleServiceTest {
@org.junit.Test
public void execute() {
AgeRule ageRule = new AgeRule();
NameRule nameRule = new NameRule();
NationalityRule nationalityRule = new NationalityRule();
AddressRule addressRule = new AddressRule();
SubjectRule subjectRule = new SubjectRule();
RuleDto dto = new RuleDto();
dto.setAge(5);
dto.setName("张三");
dto.setAddress("北京");
dto.setSubject("数学");
boolean ruleResult = RuleService.create()
.and(Arrays.asList(nationalityRule, nameRule, addressRule))
.or(Arrays.asList(ageRule, subjectRule))
.execute(dto);
System.out.println("this student rule execute result :" + ruleResult);
}
}Conclusion
Advantages
Simple and modular; each rule is independent, making the executor clean.
The convert method in the abstract rule allows custom data transformation for specific rules.
Disadvantages
All rules share a common DTO, creating hidden data dependencies; better to construct dedicated data objects beforehand.
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.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
