Mastering Drools: Build a Phone Billing Rule Engine in Java

This article walks through designing business rules for a mobile phone billing scenario, defining Java fact objects, implementing a Drools rule engine, writing DRL files, and testing the engine to demonstrate rule‑driven credit calculations.

ITFLY8 Architecture Home
ITFLY8 Architecture Home
ITFLY8 Architecture Home
Mastering Drools: Build a Phone Billing Rule Engine in Java

Define Rules

First, analyze the business logic and create three rules: give a new user ¥20 credit, award ¥5 after three recharges between October and December 2014, and add ¥10 for each ¥100 topped up in a month.

Create Fact Object

Encapsulate the rule data in a Java POJO called EntityRule:

package com.core.drools;
import java.util.UUID;
/**
 * EntityRule‑Model
 */
public class EntityRule {
    private String username;
    /** Whether for new account. */
    private boolean account;
    /** The number of add. */
    private int addtime;
    /** The sum of the current account. */
    private double currentmoney;
    /** The total amount added. */
    private double totailaddmoney;
    public void getSerialnumber(String username,double currentmoney){
        System.out.println("Account:"+username+" Balance:¥"+currentmoney);
        System.out.println("Serial Number:"+UUID.randomUUID().toString());
    }
}

Define Rule Engine

Declare an interface RuleEngine and provide an implementation that loads DRL files, creates a RuleBase, and fires rules against an EntityRule instance.

package com.core.drools;
public interface RuleEngine {
    /** Initializes the rules engine. */
    void init();
    /** Refresh the rules engine. */
    void refresh();
    /** Execute the rules engine. */
    void execute(final EntityRule entityRule);
}
package com.core.drools;
import java.io.*;
import java.util.*;
import org.drools.*;
import org.drools.compiler.*;
import org.drools.rule.*;
import org.drools.spi.*;
public class RuleEngineImpl implements RuleEngine {
    private RuleBase ruleBase;
    @Override
    public void init() {
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        ruleBase = SingleRuleFactory.getRuleBase();
        try {
            PackageBuilder packageBuilder = getPackageBuilderFile();
            ruleBase.addPackages(packageBuilder.getPackages());
        } catch (DroolsParserException | IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void refresh() {
        ruleBase = SingleRuleFactory.getRuleBase();
        for (Package p : ruleBase.getPackages()) {
            ruleBase.removePackage(p.getName());
        }
        init();
    }
    @Override
    public void execute(final EntityRule entityRule) {
        if (ruleBase.getPackages() == null || ruleBase.getPackages().length == 0) return;
        StatefulSession session = ruleBase.newStatefulSession();
        session.insert(entityRule);
        session.fireAllRules(new AgendaFilter() {
            public boolean accept(Activation activation) {
                return !activation.getRule().getName().contains("_test");
            }
        });
        session.dispose();
    }
    private PackageBuilder getPackageBuilderFile() throws Exception {
        List<String> drlFilePath = getRuleFile();
        List<Reader> readers = loadRuleFile(drlFilePath);
        PackageBuilder packageBuilder = new PackageBuilder();
        for (Reader r : readers) {
            packageBuilder.addPackageFromDrl(r);
        }
        if (packageBuilder.hasErrors()) {
            throw new Exception(packageBuilder.getErrors().toString());
        }
        return packageBuilder;
    }
    private List<Reader> loadRuleFile(List<String> drlFilePath) throws FileNotFoundException {
        if (drlFilePath == null || drlFilePath.isEmpty()) return null;
        List<Reader> readers = new ArrayList<>();
        for (String path : drlFilePath) {
            readers.add(new FileReader(new File(path)));
        }
        return readers;
    }
    private List<String> getRuleFile(){
        List<String> drlFilePath = new ArrayList<>();
        String path = "D:/utils/my/DroolsProject/src/com/core/drools/drools_rule.drl";
        drlFilePath.add(path);
        return drlFilePath;
    }
}

Singleton RuleBase Factory

package com.core.drools;
import org.drools.*;
public class SingleRuleFactory {
    private static RuleBase ruleBase;
    public static RuleBase getRuleBase(){
        return ruleBase != null ? ruleBase : RuleBaseFactory.newRuleBase();
    }
}

Write DRL Rule File

The DRL file defines three rules that correspond to the business scenarios.

package com.core.drools
import com.core.drools.EntityRule;
rule accountEntity
    salience 100
    lock-on-active true
when
    $entityRule : EntityRule(account == true)
then
    System.out.println("The new account:Present ¥20.0");
    $entityRule.setCurrentmoney($entityRule.getCurrentmoney()+20);
    $entityRule.getSerialnumber($entityRule.getUsername(), $entityRule.getCurrentmoney());
end

rule billEntity
    salience 99
    lock-on-active true
    date-effective "2014-01-01 00:00:00"
    date-expires "2014-12-31 23:59:59"
when
    $entityRule : EntityRule(addtime >= 3)
then
    System.out.println("Prepaid phone number reach "+$entityRule.getAddtime()+" times:Present ¥"+($entityRule.getAddtime()/3*5));
    $entityRule.setCurrentmoney($entityRule.getCurrentmoney()+$entityRule.getAddtime()/3*5);
    $entityRule.getSerialnumber($entityRule.getUsername(), $entityRule.getCurrentmoney());
end

rule addMoney
    salience 98
    lock-on-active true
when
    $entityRule : EntityRule(totailaddmoney >= 100)
then
    System.out.println("The account for the month top‑up total amount is "+$entityRule.getTotailaddmoney()+":Present ¥"+((int)$entityRule.getTotailaddmoney()/100*10));
    $entityRule.setCurrentmoney($entityRule.getCurrentmoney()+((int)$entityRule.getTotailaddmoney()/100*10));
    $entityRule.getSerialnumber($entityRule.getUsername(), $entityRule.getCurrentmoney());
end

Test the Engine

package com.test;
import com.core.drools.*;
public class DroolsTest {
    public static void main(String[] args) throws IOException {
        RuleEngine engine = new RuleEngineImpl();
        engine.init();
        EntityRule entity = new EntityRule();
        entity.setCurrentmoney(350d);
        entity.setUsername("Candy");
        entity.setAccount(true);
        entity.setTotailaddmoney(350d);
        entity.setAddtime(7);
        engine.execute(entity);
    }
}

Test Results

The console output shows the sequential application of the three rules:

The new account:Present ¥20.0
Account:Candy Balance:¥370.0
Serial Number:0fd98593-caa2-444d-a4ff-b4001cfb3260
--------------------------------------------------
Prepaid phone number reach 7 times:Present ¥10
Account:Candy Balance:¥380.0
Serial Number:a118b211-c28e-4035-aa60-2f417f62b2f3
--------------------------------------------------
The account for the month top‑up total amount is 350.0: Present ¥30
Account:Candy Balance:¥410.0
Serial Number:2bfc02c2-267f-9cda-5a4196e2b7cf

This demonstration illustrates how Drools can externalize and manage changing business rules, making the application logic more flexible and maintainable.

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.

Business RulesDrools
ITFLY8 Architecture Home
Written by

ITFLY8 Architecture Home

ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.

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.