The RuleBook DSL

given / when / except / then / using / stop, explained.

On this page

The RuleBook Domain Specific Language

The RuleBox DSL uses the Given-When-Then format, popularized by Behavior Driven Development (BDD) and testing frameworks (TestBox, Cucumber, Spock), and heavily inspired by RuleBox's Java counterpart, RuleBook. Sentences should describe rules, and rules should be defined using a ubiquitous language that translates directly into the codebase.

Given-When-Then

  • Given - some Fact(s)
  • When - a condition evaluates to true
  • Except - a condition that evaluates to false
  • Then - an action is triggered

when() conditions can grow unwieldy, so RuleBox adds except() to the language: when().except().then(). Even when the when() condition evaluates to true, chaining an except() onto it that evaluates to true cancels the rule for that run.

given() / givenAll()

given()/givenAll() accept one or more facts, in various forms, as a collection of information provided to a single Rule. When grouping Rules into a RuleBook, facts are supplied when the RuleBook is run, so the "Given" step is inferred:

var homeLoans = getInstance( "tests.resources.HomeLoanRateRuleBook" )
	.withDefaultResult( 4.5 )
	.given( "creditScore", 650 )
	.given( "cashOnHand", 20000 )
	.given( "firstTimeHomeBuyer", false )
	.run()

var homeLoans = getInstance( "tests.resources.HomeLoanRateRuleBook" )
	.withDefaultResult( 4.5 )
	.givenAll( {
		creditScore: 650,
		cashOnHand: 20000,
		firstTimeHomeBuyer: false
	} )
	.run()

givenAll() accepts a second overwrite argument (default true) that controls whether facts already present are replaced.

when()

when() accepts a Predicate closure/lambda that evaluates a condition based on the facts provided. Only one when() can be specified per rule, and it must return boolean:

.when( ( facts ) => {
	// determine if we continue or not
	return boolean
} )

except()

except() negates the when() operation if it also passes. In other words: when the balance is greater than 100, except when the account is disabled, then dispense some money:

except( ( facts ) => facts.accountDisabled )

then()

then() accepts a Consumer closure/lambda describing the action to run if when() evaluates to true. A Rule can specify multiple then() calls, invoked in the order they're declared. If a then() returns true, no further consumers in that rule execute - the chain breaks. Returning void or false continues the chain:

.then( ( facts, result ) => {
	// do stuff

	// break the next then()
	return true
} )
.then( ( facts, result ) => {
	// This never fires
} )

using()

using() reduces the set of facts available to the next then() call. Multiple using() calls can be chained together - the aggregate of every fact name across them is what's made available to the immediately following then():

addRule(
	newRule()
		.when( ( facts ) => facts.keyExists( "hello" ) && facts.keyExists( "world" ) )
		using( "hello" ).then( ( facts ) => println( facts.hello ) )
		using( "world" ).then( ( facts ) => println( facts.world ) )
)

withPriority()

By default, rules execute strictly in the order they're added via addRule(). withPriority() lets a rule jump the queue - a higher priority runs earlier. Rules that share the same priority (the default is 0) keep insertion order relative to each other:

addRule(
	newRule( "checkBlocklist" )
		.withPriority( 10 )
		.when( ( facts ) => facts.applicant.isBlocklisted() )
		.then( ( facts, result ) => result.setValue( 0 ) )
		.stop()
)

addRule(
	newRule( "creditScoreAdjustment" )
		// no withPriority() - defaults to 0, runs after the rule above
		.when( ( facts ) => facts.applicant.getCreditScore() < 600 )
		.then( ( facts, result ) => result.setValue( result.getValue() * 4 ) )
)

withPriority() can be called before or after a rule is added to a RuleBook - the RuleBook re-derives its entire execution chain, sorted by priority (ties broken by insertion order), every time addRule() is called, so a later, higher-priority rule correctly slots ahead of rules already registered.

stop()

stop() breaks the rule chain. If specified on a rule whose when() evaluates to true, then once that rule's then() action(s) complete, no further rules in the chain are evaluated.

addRule(
	newRule()
		.when( ( facts ) => facts.applicant.getCreditScore() < 600 )
		.then( ( facts, result ) => result.setValue( result.getValue() * 4 ) )
		.stop()
)
Edit this page Download Markdown Last updated Sep 17, 2026, 7:29:54 PM