RuleBox is a modern intuitive and natural language rule engine based upon the great work of RuleBook: https://github.com/rulebook-rules/rulebook ported over to ColdFusion (CFML).
Tired of classes filled with if/then/else statements? Need a nice abstraction that allows rules to be easily specified in a way that decouples them from each other? Want to write rules the same way that you write the rest of your code [in ColdFusion]? RuleBox is right for you!
3. WHO AM I?
• Luis Majano
• Computer Engineer
• Born in El Salvador ->Texas
• CEO of Ortus Solutions
• Sandals -> ESRI -> Ortus
@lmajano
@ortussolutions
4. What is RuleBox
Why RuleBox was created
The Problem
What is a Rule Engine
Why use a Rule Engine
Implementation of RuleBox
5. What is RuleBox
• A modern and natural language rule engine
• Inspired by RuleBook (Java Project)
• Rules are written in the RuleBox Domain Specific Language (DSL)
• Modeled after Given-When-Then
• Dynamic and Expressive
https://forgebox.io/view/rulebox
Reference: https://github.com/rulebook-rules/rulebook
install rulebox
6. Why Create RuleBox
• Thanks to RevAgency for inspiration:
• Real world problem to handle cruise promotions
• Legacy app plagued with nested if-then-else
• Logic and data mixed
• Much more…
• Did my research:
• Majority of rule engines are complex and convoluted
• Nothing in ColdFusion (CFML) Landscape
• Found RuleBook articles inspiring
• Lots of caffeine, and RuleBox was born!
8. The Problem
• Ever nesting of if-then-else logic
• Imperative algorithms showing how instead of what?
• No Logic and Data Separation
• Logic is not easily followed
• Maintenance pain
• How testable is that code? Has it really been tested?
• The Domino Effect:
Single if statement can rule them all
9. What is a rule engine?
• Alternative computational model to traditional imperative programming
• Imperative: Sequence of commands with conditionals, loops and data
• Set of rules that have a condition and an action based on data=>facts
• The engine evaluates rules in the appropriate order and make sense of it
• You focus on the conditions and actions and not the how you do it.
10. Advantages
• Functional and Declarative Programming
• Speed and Scalability of development
• Understandable and Named Rules
• Increase Maintenance
• Increase Encapsulation of Logic
• Logic and Data Separation
12. When…
• Remember the problem
• Logic and data intermixed
• Maintenance is painful
• Logic is brittle
• The logic changes often
• 500 lines of if-else statements
16. Chain of Responsibility
• RuleBox Models CoR
• A RuleBook defines rules and chains them
together to provider order
• Facts flow to each rule (data-binding)
• RuleBooks control results
• Each Rule has a condition, an exception
and one or more actions
https://sourcemaking.com/design_patterns/chain_of_responsibility
18. WireBox Mappings
• Rule@rulebox - A transient rule object
• RuleBook@rulebox - A transient rule book object
• Builder@rulebox - A static class that can be used to build a-la-carte rules
and rulebooks.
• Result@rulebox - RuleBooks produce results and this is the object that
models such results. Similar to Java Optionals
19. RuleBox DSL
• Given( name, value ) - The facts
• GivenAll( struct ) - Struct of facts
• When( closure/lambda ) - The conditions
• Except( closure/lambda ) - The exceptions
• Then( closure/lambda ) - The actions/consumers
Rules
Against a RuleBook
20. RuleBooks
component extends="rulebox.models.RuleBook"{
function defineRules(){
setName( “My RuleBook” );
// Add a new rule to this rulebook
addRule(
newRule( "MyRule" )
.when( function( facts ){
return facts.keyExists( “hello” );
} )
.then( function( facts ){
systemOutput( "World" );
} )
);
}
}
• ATransient CFC inherits from rulebox.models.RuleBook
• Can have a name property
• 1 Method: defineRules()
• Create rules using the DSL
21. Creating Rules
addRule(
newRule( “name” )
.when( function( facts ){
} )
.except( function( facts ){
} )
.then( function( facts, result ) {
} )
)
.addRule( function( rule ){
rule
.setName( ‘name’ )
.when( function( facts ){
} )
.except( function( facts ){
} )
.then( function( facts, result ) {
} )
.stop();
} )
• Using the addRule( rule ) method
• Alternative Syntax, choose your comfort!
• Each Rule can have the following:
• Name (optional)
• 1 when()
• 0,1 except()
• * then()
• 1 stop()
• Best friend:Api Docs
https://apidocs.ortussolutions.com/#/coldbox-modules/rulebox/
22. When()
addRule(
newRule()
.when( function( facts ){
return facts.keyExists( "hello" );
})
.then( function( facts ){
sytemOutput( facts.hello );
} )
)
• The condition portion
• Argument is a closure/lambda
• Must evaluate to boolean
• True => Executes Consumers (thens)
• False => Rule does not apply, skip consumers
• Receives facts as a struct
23. Except()
addRule(
newRule()
.when( function( facts ){
return facts.keyExists( "hello" );
})
.except( function( facts ){
return facts.accountDisabled;
} )
.then( function( facts ){
sytemOutput( facts.hello );
} )
)
• Negates the when() condition
• True => Skip consumers (thens)
• False => Continue to consumers
• Special cases and of course exceptions
• Why?To not pollute the when() with negated logic
24. Consumers : then()
then( function( facts, result ){ sytemOutput( facts.world ); } )
then( function( facts, result ){ result.setValue( result.getValue() * 0.80 ); } )
• The actions for your logic
• Closure that receives
• Facts => A struct of facts
• Results => A RuleBox Result object
• Can have multiple actions
• If the action returns true then it stops the chain of consumers
• Best friend:Api Docs
https://apidocs.ortussolutions.com/#/coldbox-modules/rulebox/
25. Stopping Consumers
.then( function( facts, result ) ){
// do stuff
// break the next then()
return true;
})
.then( function( facts, result ) ){
// This never fires
})
26. Stopping Rules
//credit score under 600 gets a 4x rate increase
addRule(
newRule()
.when( function( facts ){ return facts.applicant.getCreditScore() < 600; } )
.then( function( facts, result ){ result.setValue( result.getValue() * 4 ); } )
.stop()
);
• stop() on a rule
• Breaks the rule chain
• No more rules are processed if a stop() is issued
27. Result Object
• Rules can work on a result object’s value (rulebox.models.Result)
• Seed it with a default value
Operation Description
reset() Reset the value with the default value
getValue() Get the value
getDefaultValue() Get the default value (if any)
isPresent() Verifies the value is not null
ifPresent( consumer ) If a value is present, the consumer will be called and the value passed to it.
orElse( other ) If value is not set, then return other else the actual value
orElseGet( producer ) If value is not set, then call the producer and return its value, else return the
actual value
setValue() Set the value in the result object
setDefaultValue() Override the default value in the result object
31. Executing Rules
• Create the RuleBook and then we can:
• Set a result default value: withDefaultResult()
• Bind a fact: given( name, value )
• Bind multiple facts: givenAll( struct )
• Execute the rules: run( {facts} )
• Retrieve the Result object (if any): getResult()
32. describe( "Home Loan Rate Rules", function(){
it( "Can calculate a first time home buyer with 20,000 down and 650 credit score", function(){
var homeLoans = getInstance( "tests.resources.HomeLoanRateRuleBook" )
.withDefaultResult( 4.5 )
.given( "creditScore", 650 )
.given( "cashOnHand", 20000 )
.given( "firstTimeHomeBuyer", true );
homeLoans.run();
expect( homeLoans.getResult().isPresent() ).toBeTrue();
expect( homeLoans.getResult().getValue() ).toBe( 4.4 );
});
it( "Can calculate a non first home buyer with 20,000 down and 650 credit score", function(){
var homeLoans = getInstance( "tests.resources.HomeLoanRateRuleBook" )
.withDefaultResult( 4.5 )
.givenAll( {
"creditScore" : 650,
"cashOnHand" : 20000,
"firstTimeHomeBuyer" : false
} );
homeLoans.run();
expect( homeLoans.getResult().isPresent() ).toBeTrue();
expect( homeLoans.getResult().getValue() ).toBe( 5.5 );
});
});
33. Auditing Rules
• All rules are audited for execution
• Important to name them in a human readable form
• Else, love the UUID created for you
• Methods for status:
• getRuleStatus( ruleName ) - Get a single rule status
• getRuleStatusMap() - All rule statuses
Status Description
NONE Rule never executed (default)
SKIPPED Rule was skipped
EXECUTED Rule was executed
34. Still in infancy
Need your feedback
Stay true to simplicity
Focus on Functional Programming
Nested Rules
StatusVisualizer
Dynamic rules from DB/Storage
More Docs
More Samples
Roadmap