DEV Community

Cover image for LWRE: A Lightweight Java Rule Engine for Scalable, Dynamic Business Logic
Ghassen hamdi
Ghassen hamdi

Posted on

LWRE: A Lightweight Java Rule Engine for Scalable, Dynamic Business Logic

Hello Dev.to community! šŸ‘‹ I’m thrilled to introduce LWRE (Lightweight Java Rule Engine), an open-source Java-based rule engine designed to tackle complex business logic with simplicity, performance, and scalability. If you’re building systems that require dynamic rule execution—think validation pipelines, decision trees, or workflow automation—LWRE is here to make your life easier. Check it out at HamdiGhassen/lwre (Apache 2.0 licensed), and if you find it valuable, please give it a ⭐ to help spread the word! 🌟.

What is LWRE?

LWRE is a Java rule engine that lets you define, compile, and execute business rules using a powerful Domain-Specific Language (DSL). It’s built for developers who need flexibility without sacrificing performance or reliability. Whether you’re managing microservices, validating user inputs, or orchestrating workflows, LWRE provides a robust framework with features like dependency management, parallel execution, and fault tolerance.

Key Features

  • Expressive DSL: Define rules with a clean syntax, supporting imports, variables, retries, timeouts, and control flow.
  • Dynamic Compilation: Uses Janino to compile rules into Java classes at runtime, with caching for efficiency.
  • Dependency Management: Organizes rules into directed acyclic graphs (DAGs) for optimized execution order, with cycle detection.
  • Parallel Execution: Executes rule groups asynchronously using a thread pool, with configurable timeouts.
  • Fault Tolerance: Supports retry policies with delays and conditions, plus a circuit breaker to prevent system overload.
  • Thread Safety: Built with ConcurrentHashMap, thread-local pools, and synchronized methods for concurrent environments.
  • Metrics & Tracing: Tracks execution metrics and supports debugging with optional tracing.
  • Rule Versioning: Enables safe updates and rollbacks for experimentation.
  • Security: Restricts access to dangerous classes (e.g., java.lang.Runtime) and enforces timeouts to prevent infinite loops.

Why Choose LWRE?

LWRE is lightweight and focused on simplicity, making it ideal for projects where you need dynamic rules without complex setup. Its DSL is intuitive, and its performance optimizations—like cached compilation and precomputed execution paths—ensure it scales well in production.

Getting Started

Prerequisites

  • Java 17 or higher
  • Maven for dependency management

Installation

Add the Janino dependency to your pom.xml:

<dependency>
    <groupId>org.codehaus.janino</groupId>
    <artifactId>janino</artifactId>
    <version>3.1.12</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Clone and build LWRE:

git clone https://github.com/HamdiGhassen/lwre.git
cd lwre
mvn clean install
Enter fullscreen mode Exit fullscreen mode

In-Depth Example: User Validation Workflow

Let’s walk through a practical example of using LWRE to validate a user and compute a credit score, with dependencies and retries. Below is a DSL defining two rules in a workflow:

#GLOBAL
userId : String
creditThreshold : Integer

#HELPER
int computeCreditScore(int base) {
return base * 2 + 50;
}

#RULE CheckUser
#GROUP UserWorkflow
#PRIORITY 1
#TIMEOUT 500 ms

#USE
userId : String as user FROM Global
creditThreshold : Integer as threshold FROM Global

#PRODUCE
isValid : Boolean

#CONDITION
return user != null && user.length() > 0;

#ACTION
isValid = true;

#FINAL
return isValid ? "User " + user + " is valid" : "Invalid user";

#RULE ComputeScore
#GROUP UserWorkflow
#PRIORITY 2
#NEXT_ON_SUCCESS Finalize
#USE
userId : String as user FROM Global
creditThreshold : Integer as threshold FROM Global
isValid : Boolean as isValid FROM RULE CheckUser

#PRODUCE
score : Integer

#CONDITION
return isValid && threshold >= 100;

#ACTION
score = computeCreditScore(threshold);
System.out.println("Computed score for " + user + ": " + score);

#FINAL
return score;
Enter fullscreen mode Exit fullscreen mode

Explanation

  • CheckUser Rule: Validates the userId and sets isValid.
  • ComputeScore Rule: Depends on CheckUser (via isValid) and computes a credit score if the user is valid. It triggers a Finalize rule (not shown) on success.
  • Dependency Management: LWRE’s RuleGraphProcessor ensures CheckUser runs before ComputeScore by building a DAG.
  • Global & Helper: Shared variables (userId, creditThreshold) and a reusable computeCreditScore method are defined globally.

Running the Workflow

Here’s how to execute this in Java:

import org.pulse.lwre.core.LWREngine;

public class Main {
    public static void main(String[] args) throws Exception {
        // Load DSL from file or string (dslContent)
        LWREngine engine = new LWREngine.Builder()
                .rules(dslContent)
                .global("userId", "john_doe")
                .global("creditThreshold", 150)
                .debug(true) // Enable tracing
                .build();

        // Execute synchronously
        Object result = engine.executeRules("UserWorkflow");
        System.out.println("Result: " + result);

        // Or execute asynchronously
        engine.executeRulesAsync("UserWorkflow")
                .thenAccept(res -> System.out.println("Async Result: " + res))
                .exceptionally(throwable -> {
                    System.err.println("Error: " + throwable.getMessage());
                    return null;
                });
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Assuming the DSL is loaded and executed:

Computed score for john_doe: 350
Result: 350
Enter fullscreen mode Exit fullscreen mode

Advanced Features

Dependency Management

LWRE’s RuleGraphProcessor uses topological sorting to determine rule execution order based on dependencies (e.g., #USE ... FROM directives). It detects cycles to prevent infinite loops, ensuring robust workflows.

Circuit Breaker

The CircuitBreaker class monitors rule failures. If a rule exceeds a failure threshold, it temporarily halts execution, preventing system overload. This is critical for high-throughput systems.

Performance Optimizations

  • Cached Compilation: Rules are compiled once and cached, reducing overhead.
  • Precomputed Paths: Execution order is precomputed using DAGs, minimizing runtime decisions.
  • Thread-Local Pools: Context maps are reused to reduce memory allocation.
  • Parallel Execution: Independent rule groups run concurrently via ForkJoinPool, with ScheduledExecutorService enforcing timeouts.

Security

LWRE restricts access to dangerous classes (e.g., java.lang.Runtime, java.io.File) and methods (e.g., exec, exit). The DSLParser validates syntax to prevent misuse, and timeouts ensure rules don’t hang.

Performance Insights

In internal tests, LWRE handles thousands of rules per second on a single thread, with parallel execution scaling linearly across cores. Compilation caching reduces rule execution latency by up to 80% after the first run.

Use Cases

  • Business Rules: Implement pricing rules, discounts, or eligibility checks.
  • Data Pipelines: Validate and transform data in ETL processes.
  • Workflow Automation: Coordinate tasks with dependencies and retries.

Try LWRE Today!

LWRE is a lightweight, powerful solution for dynamic rule execution in Java.It’s secure and optimized for performance. I invite you to explore the GitHub repo, try the examples, and contribute ideas or feedback. If you like what you see, a ⭐ on GitHub would help others discover this project! šŸ™Œ

Have questions? Want to share how you’re using LWRE? Drop a comment below, open an issue on GitHub, or reach out to me directly. Let’s build something amazing together! šŸ’»

Top comments (0)