Understanding Rule Units in Aletyx Enterprise Build of Drools 10.0.0¶
Introduction to Rule Units¶
Rule Units represent the modern approach to organizing business rules in Drools. Introduced in Drools 7 and significantly enhanced in subsequent versions, Rule Units provide a structured, modular way to organize rules and their associated data.
In traditional Drools applications, all rules shared a global working memory (KieSession) with little structure or isolation. Rule Units solve this problem by encapsulating rules with their own data sources, creating self-contained modules that can be developed, tested, and executed independently.
Core Concepts of Rule Units¶
A Rule Unit consists of these key components:
- A Rule Unit Class: A Java class that implements
RuleUnitData
(orRuleUnit
in older versions) - Data Sources: Typed containers for facts that rules operate on
- Global Variables: Values shared across all rules in the unit
- Rules: DRL rules that belong to the unit
- Queries: Named operations for retrieving data from the unit
Let's explore each component in detail.
Rule Unit Classes¶
A Rule Unit class is a Java class that serves as the container for data sources and global variables. This class implements the RuleUnitData
interface, which is a marker interface (has no methods to implement).
Basic Rule Unit Class Structure¶
package org.example;
import org.drools.ruleunits.api.RuleUnitData;
import org.drools.ruleunits.api.DataStore;
import org.drools.ruleunits.api.DataSource;
public class OrderProcessingUnit implements RuleUnitData {
// Data sources
private final DataStore<Customer> customers;
private final DataStore<Order> orders;
// Global variables
private double taxRate;
private boolean expressShipping;
// Constructor
public OrderProcessingUnit() {
this.customers = DataSource.createStore();
this.orders = DataSource.createStore();
this.taxRate = 0.07;
this.expressShipping = false;
}
// Getters and setters
public DataStore<Customer> getCustomers() {
return customers;
}
public DataStore<Order> getOrders() {
return orders;
}
public double getTaxRate() {
return taxRate;
}
public void setTaxRate(double taxRate) {
this.taxRate = taxRate;
}
public boolean isExpressShipping() {
return expressShipping;
}
public void setExpressShipping(boolean expressShipping) {
this.expressShipping = expressShipping;
}
}
This class defines:
- Two data sources:
customers
andorders
- Two global variables:
taxRate
andexpressShipping
- Standard getters and setters for all members
Rule Unit Lifecycle Methods¶
In addition to data sources and global variables, Rule Unit classes can optionally implement lifecycle methods:
package org.example;
import org.drools.ruleunits.api.RuleUnitData;
import org.drools.ruleunits.api.RuleUnitInstance;
public class AuditableRuleUnit implements RuleUnitData {
// Data sources and variables
// Lifecycle methods
public void onStart() {
System.out.println("Rule unit starting: " + this.getClass().getSimpleName());
// Initialize resources, start timers, etc.
}
public void onEnd() {
System.out.println("Rule unit ending: " + this.getClass().getSimpleName());
// Clean up resources, log completion, etc.
}
public void onSuspend() {
System.out.println("Rule unit suspended");
// Called when using runUntilHalt() and then halt()
}
public void onResume() {
System.out.println("Rule unit resumed");
// Called when resuming after halt()
}
public void onYield(RuleUnitInstance<? extends RuleUnitData> other) {
System.out.println("Rule unit yielding to: " + other.unit().getClass().getSimpleName());
// Called when execution passes to another unit
}
}
These lifecycle methods provide hooks for setup, teardown, and coordination between rule units.
Data Sources in Rule Units¶
Data sources are typed containers for facts that rules operate on. They represent the entry points through which data flows into your rules. Rule Units support three types of data sources:
1. DataStore¶
A DataStore is a mutable collection that supports the full CRUD (Create, Read, Update, Delete) lifecycle for facts.
// Creating a DataStore
private DataStore<Customer> customers = DataSource.createStore();
// Adding a fact
DataHandle handle = customers.add(new Customer("John", "Doe"));
// Updating a fact
customer.setStatus("PREMIUM");
customers.update(handle, customer);
// Removing a fact
customers.remove(handle);
2. DataStream¶
A DataStream is an append-only collection designed for event processing.
// Creating a DataStream
private DataStream<LogEvent> events = DataSource.createStream();
// Appending an event
events.append(new LogEvent("USER_LOGIN", user.getId(), timestamp));
3. SingletonStore¶
A SingletonStore holds a single value that can be updated or cleared.
// Creating a SingletonStore
private SingletonStore<Configuration> config = DataSource.createSingleton();
// Setting a value
config.set(new Configuration("PRODUCTION", 100));
// Updating the value
Configuration current = config.get();
current.setMaxConnections(150);
config.update();
// Clearing the value
config.clear();
DRL Files with Rule Units¶
DRL files for Rule Units follow a specific structure that associates the rules with a particular unit.
Basic Structure¶
package org.example;
unit OrderProcessingUnit; //(1)!
import org.example.Customer;
import org.example.Order;
rule "Apply Tax"
when
$order: /orders[ status == "CONFIRMED", taxApplied == false ]
then
$order.setTaxAmount($order.getSubtotal() * taxRate); //(2)!
$order.setTaxApplied(true);
orders.update($order); //(3)!
end
- Declare the unit just after the package of the DRL file
- The unit provides direct access to global variables (like
taxRate
) - Notifying the engine that we've updated the unit
The key elements are:
- The unit
declaration, which associates the file with a specific Rule Unit class
- OOPath expressions starting with /
to reference data sources
- Direct access to global variables (like taxRate
)
- Data source operations in rule actions (like orders.update($order)
)
Accessing Data Sources in Rules¶
Data sources are accessed using OOPath syntax, starting with a forward slash:
rule "Gold Customer Detection"
when
$customer: /customers[ totalSpent > 10000, status != "GOLD" ]
then
$customer.setStatus("GOLD");
customers.update($customer);
end
Accessing Global Variables¶
Global variables defined in the Rule Unit class are directly accessible in the rules:
rule "Apply Express Shipping"
when
$order: /orders[ priority == "HIGH" ]
then
if (expressShipping) {
$order.setShippingMethod("EXPRESS");
} else {
$order.setShippingMethod("STANDARD");
}
orders.update($order);
end
Defining Queries¶
Queries allow you to extract data from Rule Units:
query "FindGoldCustomers"
$customer: /customers[ status == "GOLD" ]
end
query "FindOrdersByCustomer" (String customerId)
$order: /orders[ customerId == customerId ]
end
Declaring Rule Units in DRL¶
Instead of creating a Java class, you can declare Rule Units directly in DRL:
package org.example;
unit CustomerUnit;
import org.drools.ruleunits.api.RuleUnitData;
import org.drools.ruleunits.api.DataStore;
import org.example.Customer;
import org.example.Order;
declare CustomerUnit extends RuleUnitData
customers: DataStore<Customer>
orders: DataStore<Order>
minimumOrderValue: double
end
rule "Identify Valuable Customers"
when
$customer: /customers
accumulate(
$order: /orders[ customerId == $customer.id, value > minimumOrderValue ];
$count: count($order);
$count > 3
)
then
$customer.setValuedCustomer(true);
customers.update($customer);
end
This approach allows you to define Rule Units without separate Java classes, which can be convenient for simpler use cases or prototyping.
Executing Rule Units¶
Rule Units are executed using the RuleUnitInstance
API.
Basic Execution¶
// Create a rule unit
OrderProcessingUnit orderUnit = new OrderProcessingUnit();
// Add some facts
orderUnit.getCustomers().add(new Customer("C1", "John Doe"));
orderUnit.getOrders().add(new Order("O1", "C1", 150.0));
// Create a rule unit instance
RuleUnitInstance<OrderProcessingUnit> instance =
RuleUnitProvider.get().createRuleUnitInstance(orderUnit);
try {
// Execute the rules
instance.fire();
// Or execute a specific query
List<Map<String, Object>> results =
instance.executeQuery("FindOrdersByCustomer", "C1").toList();
} finally {
// Always dispose the instance when done
instance.dispose();
}
Using RuleUnitExecutor¶
For more advanced scenarios, especially when working with multiple rule units, you can use the RuleUnitExecutor
:
// Create an executor
RuleUnitExecutor executor = RuleUnitExecutor.create();
// Bind to a KieContainer if needed
KieContainer kieContainer = KieServices.get().getKieClasspathContainer();
executor.bind(kieContainer);
// Create and register a rule unit
OrderProcessingUnit orderUnit = new OrderProcessingUnit();
orderUnit.getCustomers().add(new Customer("C1", "John Doe"));
orderUnit.getOrders().add(new Order("O1", "C1", 150.0));
// Execute the unit
executor.run(orderUnit);
// Or use the class-based approach with variable binding
executor.bindVariable("customers", DataSource.createStore(
new Customer("C1", "John Doe")));
executor.bindVariable("orders", DataSource.createStore(
new Order("O1", "C1", 150.0)));
executor.bindVariable("taxRate", 0.07);
executor.run(OrderProcessingUnit.class);
Continuous Execution¶
For event-based systems, you can use the runUntilHalt()
method:
// Start continuous execution in a separate thread
new Thread(() -> {
executor.runUntilHalt(eventProcessingUnit);
}).start();
// Later, stop execution
executor.halt();
Rule Unit Coordination¶
One of the powerful features of Rule Units is the ability to coordinate the execution of multiple units.
Explicit Coordination¶
// Execute units in sequence
executor.run(validationUnit);
executor.run(processingUnit);
executor.run(notificationUnit);
Data-Driven Coordination¶
// First unit generates data for the second
ValidationUnit validationUnit = new ValidationUnit();
ProcessingUnit processingUnit = new ProcessingUnit();
// Populate validation unit
populateValidationUnit(validationUnit);
// Execute validation
executor.run(validationUnit);
// Transfer results to processing unit
Collection<ValidationResult> results =
validationUnit.getResults().stream().collect(Collectors.toList());
results.forEach(processingUnit.getValidatedData()::add);
// Execute processing
executor.run(processingUnit);
Event-Driven Coordination¶
For more complex coordination patterns, you can use events to trigger rule units:
rule "Order Validated"
when
$order: /orders[ status == "VALIDATED" ]
then
// Signal that this order is ready for processing
orderEvents.append(new OrderEvent($order.getId(), "READY_FOR_PROCESSING"));
end
Then in another rule unit:
rule "Process Ready Order"
when
$event: /orderEvents[ type == "READY_FOR_PROCESSING" ]
$order: /orders[ id == $event.orderId ]
then
// Process the order
processOrder($order);
orders.update($order);
end
Advanced Rule Unit Features¶
Configuring Rule Units¶
You can configure rule units using RuleConfig
:
// Create a configuration
RuleConfig ruleConfig = new RuleConfig();
// Add event listeners
ruleConfig.addRuleRuntimeEventListener(new MyRuleRuntimeListener());
ruleConfig.addAgendaEventListener(new MyAgendaEventListener());
// Create a rule unit instance with the configuration
RuleUnitInstance<OrderProcessingUnit> instance =
RuleUnitProvider.get().createRuleUnitInstance(orderUnit, ruleConfig);
Rule Unit DSL¶
For more complex rule unit interactions, you can use the Rule Unit DSL:
// Create and configure the rule units
ValidationUnit validationUnit = new ValidationUnit();
ProcessingUnit processingUnit = new ProcessingUnit();
NotificationUnit notificationUnit = new NotificationUnit();
// Build a rule unit DSL
RuleUnitDsl.builder()
.bind(kieContainer)
.onStart(unit -> System.out.println("Starting: " + unit))
.onEnd(unit -> System.out.println("Completed: " + unit))
.execute(validationUnit)
.onYield(validationUnit, (source, target) -> {
// Transfer data between units
transferData(source, target);
})
.execute(processingUnit)
.onYield(processingUnit, (source, target) -> {
// Transfer data between units
transferData(source, target);
})
.execute(notificationUnit)
.build()
.run();
Best Practices for Rule Units¶
1. Design for Modularity¶
Break down complex rule systems into focused rule units:
// Instead of one large RuleUnit
public class AllPurposeUnit implements RuleUnitData {
// Dozens of different data sources
// Hundreds of rules
}
// Create multiple focused units
public class ValidationUnit implements RuleUnitData {
// Validation-specific data sources
// Validation-specific rules
}
public class PricingUnit implements RuleUnitData {
// Pricing-specific data sources
// Pricing-specific rules
}
public class ShippingUnit implements RuleUnitData {
// Shipping-specific data sources
// Shipping-specific rules
}
2. Choose the Right Data Sources¶
Select the appropriate data source type for each use case:
- DataStore: For facts that need full CRUD operations
- DataStream: For events and append-only data
- SingletonStore: For global configuration and shared state
3. Use Consistent Naming Conventions¶
Establish clear naming conventions for rule units and their components:
// Rule unit class
public class OrderProcessingUnit implements RuleUnitData { ... }
// DRL file
// OrderProcessingUnit.drl
// Package and unit declaration
package org.example;
unit OrderProcessingUnit;
// Rule naming
rule "OrderProcessing - Validate Order"
rule "OrderProcessing - Calculate Tax"
rule "OrderProcessing - Apply Shipping"
4. Test Rule Units in Isolation¶
Create unit tests for each rule unit:
@Test
public void testOrderValidation() {
// Create the rule unit
OrderValidationUnit unit = new OrderValidationUnit();
// Populate with test data
Order validOrder = new Order("O1", "C1", 100.0);
Order invalidOrder = new Order("O2", "C2", -50.0);
unit.getOrders().add(validOrder);
unit.getOrders().add(invalidOrder);
// Execute the rules
RuleUnitInstance<OrderValidationUnit> instance =
RuleUnitProvider.get().createRuleUnitInstance(unit);
instance.fire();
// Verify results
assertEquals("VALID", validOrder.getStatus());
assertEquals("INVALID", invalidOrder.getStatus());
assertEquals("Negative amount", invalidOrder.getErrorMessage());
}
5. Document Rule Units and Their Purpose¶
Add clear documentation to rule unit classes and DRL files:
/**
* OrderValidationUnit performs initial validation on incoming orders.
*
* Responsibilities:
* - Check order amount is positive
* - Verify customer exists
* - Validate product inventory
* - Flag suspicious orders for review
*/
public class OrderValidationUnit implements RuleUnitData {
// ...
}
Migrating from Traditional Drools to Rule Units¶
If you're transitioning from traditional Drools to Rule Units, here are the key steps:
1. Identify Rule Groups¶
Identify groups of related rules in your existing codebase:
- Rules in the same agenda group
- Rules with similar purposes
- Rules operating on the same fact types
2. Define Rule Unit Classes¶
Create Rule Unit classes for each group:
// Before: Global KieSession with different rule files
KieSession session = kieContainer.newKieSession();
// After: Specific Rule Units
CustomerUnit customerUnit = new CustomerUnit();
OrderUnit orderUnit = new OrderUnit();
3. Convert Facts to Data Sources¶
Move facts from global working memory to specific data sources:
// Before: Global working memory
for (Customer customer : customers) {
session.insert(customer);
}
// After: Typed data sources
for (Customer customer : customers) {
customerUnit.getCustomers().add(customer);
}
4. Update DRL Files¶
Add unit declarations to your DRL files:
// Before
package org.example;
rule "Gold Customer"
when
$customer: Customer(totalSpent > 10000)
then
$customer.setStatus("GOLD");
update($customer);
end
// After
package org.example;
unit CustomerUnit;
rule "Gold Customer"
when
$customer: /customers[ totalSpent > 10000 ]
then
$customer.setStatus("GOLD");
customers.update($customer);
end
5. Replace Direct Rule Operations¶
Update rule actions to use data source operations:
// Before
then
Customer newCustomer = new Customer();
insert(newCustomer);
existingCustomer.setStatus("ACTIVE");
update(existingCustomer);
delete(oldCustomer);
end
// After
then
Customer newCustomer = new Customer();
customers.add(newCustomer);
existingCustomer.setStatus("ACTIVE");
customers.update(existingCustomer);
customers.remove(oldCustomer);
end
Converting Between Legacy and Rule Unit APIs¶
Legacy API | Rule Unit API |
---|---|
KieSession |
RuleUnitInstance<T> |
insert(fact) |
dataStore.add(fact) |
update(fact) |
dataStore.update(fact) |
delete(fact) |
dataStore.remove(fact) |
getQueryResults(queryName) |
executeQuery(queryName) |
fireAllRules() |
fire() |
fireUntilHalt() |
runUntilHalt() |
Global variables | Unit variables |
Entry Points | DataStore/DataStream |
REST Integration with Rule Units¶
Rule Units integrate seamlessly with REST APIs when using frameworks like Quarkus or Spring Boot.
Automatically Generated REST Endpoints¶
When using Kogito with Rule Units, REST endpoints are automatically generated:
// This rule unit...
package org.example;
public class CustomerUnit implements RuleUnitData {
private DataStore<Customer> customers;
// ...
}
// ...automatically generates REST endpoints like:
// GET /customers
// POST /customers
// GET /customers/{id}
// PUT /customers/{id}
// DELETE /customers/{id}
// POST /CustomerUnit
Custom REST Controllers¶
You can also create custom controllers for more control:
@Path("/customers")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CustomerResource {
@Inject
RuleUnitInstance<CustomerUnit> customerUnitInstance;
@POST
@Path("/validate")
public Response validateCustomers(List<Customer> newCustomers) {
CustomerUnit unit = customerUnitInstance.unit();
// Add customers to the unit
for (Customer customer : newCustomers) {
unit.getCustomers().add(customer);
}
// Execute rules
customerUnitInstance.fire();
// Get validation results
List<Map<String, Object>> validations =
customerUnitInstance.executeQuery("FindInvalidCustomers").toList();
return Response.ok(validations).build();
}
}
Real-World Examples¶
Let's look at some real-world examples of Rule Units in action.
Example 1: Loan Application Processing¶
public class LoanProcessingUnit implements RuleUnitData {
private final DataStore<Applicant> applicants;
private final DataStore<LoanApplication> applications;
private final DataStore<CreditReport> creditReports;
private final SingletonStore<RiskThresholds> riskThresholds;
// Constructor with initialization
// Getters and setters
}
DRL file:
package org.example;
unit LoanProcessingUnit;
rule "Auto-Approve Low-Risk Applications"
when
$application: /applications[ status == "PENDING" ]
$applicant: /applicants[ id == $application.applicantId ]
$credit: /creditReports[ applicantId == $applicant.id, score > 750 ]
$thresholds: /riskThresholds[]
eval($application.requestedAmount <= $thresholds.getAutoApprovalLimit())
then
$application.setStatus("APPROVED");
$application.setApprovalReason("Excellent credit score and low loan amount");
$application.setRiskCategory("LOW");
applications.update($application);
end
rule "Flag High-Risk Applications for Review"
when
$application: /applications[ status == "PENDING" ]
$applicant: /applicants[ id == $application.applicantId ]
$credit: /creditReports[ applicantId == $applicant.id, score < 600 ]
or
$application: /applications[ status == "PENDING",
loanToIncomeRatio > 0.43 ]
then
$application.setStatus("REVIEW");
$application.setReviewReason("High risk indicators");
$application.setRiskCategory("HIGH");
applications.update($application);
end
query "FindApprovedApplications"
$application: /applications[ status == "APPROVED" ]
end
query "FindApplicationsForReview"
$application: /applications[ status == "REVIEW" ]
end
Example 2: IoT Monitoring System¶
public class SensorMonitoringUnit implements RuleUnitData {
private final DataStream<SensorReading> readings;
private final DataStore<Alert> alerts;
private final DataStore<Device> devices;
private final SingletonStore<AlertThresholds> thresholds;
// Constructor with initialization
// Getters and setters
}
DRL file:
package org.example;
unit SensorMonitoringUnit;
rule "High Temperature Alert"
when
$reading: /readings[ type == "TEMPERATURE",
$value: value, $deviceId: deviceId ]
$device: /devices[ id == $deviceId ]
$thresholds: /thresholds[]
eval($value > $thresholds.getTemperatureMax())
then
Alert alert = new Alert(
"HIGH_TEMPERATURE",
"Temperature threshold exceeded: " + $value,
$deviceId,
$device.getLocation(),
System.currentTimeMillis()
);
alerts.add(alert);
end
rule "Battery Low Alert"
when
$reading: /readings[ type == "BATTERY",
$value: value, $deviceId: deviceId ]
$device: /devices[ id == $deviceId ]
$thresholds: /thresholds[]
eval($value < $thresholds.getBatteryMin())
then
Alert alert = new Alert(
"LOW_BATTERY",
"Battery level critical: " + $value + "%",
$deviceId,
$device.getLocation(),
System.currentTimeMillis()
);
alerts.add(alert);
end
query "FindActiveAlerts"
$alert: /alerts[ resolved == false ]
end
query "FindAlertsByDevice" (String deviceId)
$alert: /alerts[ deviceId == deviceId ]
end
Common Patterns with Rule Units¶
Event-Driven Architecture¶
Rule Units fit naturally into event-driven architectures:
@ApplicationScoped
public class SensorEventProcessor {
@Inject
SensorMonitoringUnit monitoringUnit;
@Inject
RuleUnitInstance<SensorMonitoringUnit> ruleUnitInstance;
@Incoming("sensor-readings")
public void processReading(SensorReading reading) {
// Add the reading to the data stream
monitoringUnit.getReadings().append(reading);
// Rules will automatically fire
// (assuming using runUntilHalt() elsewhere)
}
@Outgoing("alerts")
@Broadcast
public Multi<Alert> streamAlerts() {
// Stream all new alerts to a Kafka topic
return Multi.createFrom().emitter(emitter -> {
monitoringUnit.getAlerts().subscribe(
DataProcessor.of(
(handle, alert) -> emitter.emit(alert)
)
);
});
}
}
Batch Processing¶
For batch processing scenarios:
@ApplicationScoped
public class BatchOrderProcessor {
@Inject
RuleUnitProvider ruleUnitProvider;
public BatchProcessingResult processBatch(List<Order> orders) {
// Create a new rule unit for this batch
OrderProcessingUnit unit = new OrderProcessingUnit();
// Load the batch data
orders.forEach(unit.getOrders()::add);
// Process the batch
try (RuleUnitInstance<OrderProcessingUnit> instance =
ruleUnitProvider.createRuleUnitInstance(unit)) {
// Run rules
instance.fire();
// Collect results
List<Order> processed = unit.getOrders().stream()
.collect(Collectors.toList());
List<Order> failed = unit.getFailedOrders().stream()
.collect(Collectors.toList());
return new BatchProcessingResult(processed, failed);
}
}
}
Multi-Stage Processing Pipeline¶
For complex workflows, chain multiple rule units:
public class OrderPipeline {
private final RuleUnitExecutor executor;
public OrderPipeline(KieContainer container) {
this.executor = RuleUnitExecutor.create().bind(container);
}
public ProcessingResult processOrder(Order order) {
// Stage 1: Validation
ValidationUnit validationUnit = new ValidationUnit();
validationUnit.getOrders().add(order);
executor.run(validationUnit);
// Check if validation passed
if (!order.isValid()) {
return new ProcessingResult(false, order,
"Validation failed: " + order.getErrorMessage());
}
// Stage 2: Pricing
PricingUnit pricingUnit = new PricingUnit();
pricingUnit.getOrders().add(order);
executor.run(pricingUnit);
// Stage 3: Inventory check
InventoryUnit inventoryUnit = new InventoryUnit();
inventoryUnit.getOrders().add(order);
executor.run(inventoryUnit);
// Check if inventory check passed
if (!order.isInStock()) {
return new ProcessingResult(false, order,
"Inventory check failed: " + order.getInventoryMessage());
}
// Stage 4: Finalization
FinalizationUnit finalizationUnit = new FinalizationUnit();
finalizationUnit.getOrders().add(order);
executor.run(finalizationUnit);
return new ProcessingResult(true, order, "Order processed successfully");
}
}
Debugging Rule Units¶
Debugging rule units requires understanding both the Rule Unit API and the underlying rule execution.
Enable Logging¶
Configure proper logging for rule execution:
// In your application.properties or logback.xml
drools.eventListenerTypes = agendaEventListener,ruleRuntimeEventListener
quarkus.log.category."org.drools".level = DEBUG
Add Event Listeners¶
Use event listeners for detailed execution information:
RuleConfig config = new RuleConfig();
config.addRuleRuntimeEventListener(new DebugRuleRuntimeEventListener());
config.addAgendaEventListener(new DebugAgendaEventListener());
RuleUnitInstance<OrderUnit> instance =
RuleUnitProvider.get().createRuleUnitInstance(orderUnit, config);
Use Breakpoints Effectively¶
Set breakpoints in: - Rule Unit lifecycle methods - Rule execution (when and then blocks) - Data source operations
Trace Execution¶
Create a simple tracing utility:
public class RuleExecutionTracer implements AgendaEventListener {
private final List<String> activations = new ArrayList<>();
@Override
public void matchCreated(MatchCreatedEvent event) {
// Record when rules are matched
activations.add("MATCH: " + event.getMatch().getRule().getName());
}
@Override
public void afterMatchFired(AfterMatchFiredEvent event) {
// Record when rules are executed
activations.add("FIRED: " + event.getMatch().getRule().getName());
}
// Implement other methods...
public List<String> getActivations() {
return activations;
}
}
Audit Queries¶
Create audit queries in your rule units:
query "DebugAllFacts"
$customer: /customers[]
$order: /orders[]
end
query "DebugNoMatchingOrders"
not /orders[]
end
Performance Considerations¶
Use the Right Data Source Type¶
Choose data source types that match your usage patterns: - DataStore for mutable, long-lived facts - DataStream for high-volume event processing - SingletonStore for configuration and shared state
Minimize Data Source Size¶
Keep data sources focused and minimal:
// Instead of one large data source
private DataStore<Order> allOrders;
// Use more focused data sources
private DataStore<Order> pendingOrders;
private DataStore<Order> completedOrders;
Consider Stateless Execution¶
For high-throughput scenarios, use stateless execution:
public OrderResult processOrder(Order order) {
// Create a fresh rule unit for each request
OrderUnit unit = new OrderUnit();
unit.getOrders().add(order);
// Execute in a new instance
RuleUnitInstance<OrderUnit> instance =
RuleUnitProvider.get().createRuleUnitInstance(unit);
try {
instance.fire();
return new OrderResult(true, order);
} finally {
instance.dispose();
}
}
Use Indexes for Better Performance¶
Ensure your fact classes have proper indexing:
@Index(value = Index.ConstraintType.EQUAL, properties = {"id"})
public class Order {
private String id;
// ...
}
Advanced Topics¶
Rule Unit Composition¶
Compose rule units for more complex behaviors:
public class CompositeRuleUnit implements RuleUnitData {
private final ValidationUnit validationUnit;
private final ProcessingUnit processingUnit;
// Constructor
public ValidationUnit getValidationUnit() {
return validationUnit;
}
public ProcessingUnit getProcessingUnit() {
return processingUnit;
}
}
Data Source Subscription¶
Subscribe to data sources programmatically:
public void subscribeToAlerts(Consumer<Alert> alertHandler) {
monitoringUnit.getAlerts().subscribe(
DataProcessor.of(
(handle, alert) -> alertHandler.accept(alert)
)
);
}
Custom Data Sources¶
Implement custom data sources for specific needs:
public class DatabaseDataStore<T> implements DataStore<T> {
private final EntityManager entityManager;
private final Class<T> entityClass;
// Constructor
@Override
public DataHandle add(T object) {
entityManager.persist(object);
return new SimpleDataHandle(object);
}
@Override
public void update(DataHandle handle, T object) {
entityManager.merge(object);
}
@Override
public void remove(DataHandle handle) {
entityManager.remove(handle.getObject());
}
// Other methods...
}
Conclusion¶
Rule Units represent a significant advancement in rule organization and execution in Drools. By providing a structured, modular approach to rule management, they enable more maintainable, testable, and scalable rule-based systems.
Key takeaways:
- Modularity: Rule Units encapsulate rules with their data sources
- Type Safety: Strongly typed data sources prevent type-related errors
- Clarity: Clear separation of concerns with dedicated data sources
- Integration: Seamless integration with modern frameworks and architectures
- Scalability: Better performance and resource utilization
Whether you're building a new rule-based system or migrating an existing one, Rule Units provide a solid foundation for organizing and executing business rules in a modern, maintainable way.