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.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Designing a Flexible Java Rule Engine: From Concept to Code

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.

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.

Design PatternsJavarule engineCode Refactoring
Java Backend Technology
Written by

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!

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.