-
Notifications
You must be signed in to change notification settings - Fork 124
RuleBook's Java DSL
The quickest and easiest way to create a RuleBook and/or Rules is with the Java Domain Specific Language (DSL). While using the Java DSL to create a RuleBook might not be appropriate for all situations, it's important to know how to use the DSL.
RuleBook ruleBook = RuleBookBuilder.create().withResultType(String.class).withDefaultResult("unknown breed")
.addRule(rule -> rule.withFactType(Boolean.class)
.when(facts -> !facts.getValue("sheds"))
.then((facts, result) -> result.setValue("poodle")))
.addRule(rule -> rule
.when(facts -> facts.containsKey("color"))
.then((facts, result) -> result.setValue(facts.getValue("color") + " " + result.getValue())))
.build();
ruleBook.run(facts); //assume that facts was a NameValueReferableMap previously created
ruleBook.getResult().ifPresent(System.out::println);
Creating a RuleBook using the Java DSL starts with the RuleBookBuilder. RuleBookBuilder.create() creates a RuleBookBuilder that builds the default RuleBook, CoRRuleBook. Of course, if you would like to use RuleBookBuilder to build your own RuleBook, you can do that by supplying your RuleBook's class to the RuleBookBuilder's create() method, like in the following example.
RuleBook ruleBook = RuleBookBuilder.create(MyRuleBook.class).build();
A RuleBookBuilder uses an intuitive internal language within Java to construct RuleBooks and their Rules. In the example above, a RuleBook is built that has a result, denoted by withResultType() and withDefaultResult(). Then Rules are added to the RuleBookBuilder using Java's Lambda syntax. A Rule can also be added using the RuleBookBuilder by simply supplying an instance of a Rule.
An interesting thing to note is that when adding Rules using the RuleBookBuilder, the only time the Result type is specified is when the RuleBookBuilder is first created. That's because the type is propagated to the functional interface used to define the lambda expression. The bottom line is that you don't have to know the type of the Result for each Rule added using a RuleBookBuilder that already had it's Result type specified; RuleBook handles the type declaration for you.
Let's take the above example with some slight modifications.
RuleBook ruleBook = RuleBookBuilder.create().withResultType(String.class).withDefaultResult("unknown breed")
.addRule(rule -> rule.withFactType(Boolean.class)
.when(facts -> !facts.getValue("sheds"))
.then((facts, result) -> result.setValue("poodle"))
.stop())
.addRule(rule -> rule
.when(facts -> facts.containsKey("color"))
.then((facts, result) -> result.setValue(facts.getValue("color") + " " + result.getValue())))
.build();
ruleBook.run(facts); //assume that facts was a NameValueReferableMap previously created
ruleBook.getResult().ifPresent(System.out::println);
Did you see what changed? The first rule now has a stop() statement at the end. The stop() statement stops the Rule chain after the Rule with the stop() statement successfully completes. So, a few conditions have to happen in order for a Rule to stop the Rule chain in a RuleBook.
- A RuleBuilder has to include the stop() statement when building a Rule.
- The Rule that contains the stop() statement must have it's condition successfully evaluate to true.
- The Rule that contains the stop() statement must have it's action(s) successfully complete.
Once the action(s) of a Rule that contains the stop() statement complete, the Rule chain will break and execution of the RuleBook will stop.
In the example above, notice how there was no given() statement included when building a Rule inside the RuleBuilder. That's because it's not available. And why would it be? Rules are provided their facts by the RuleBook they belong to. So, when you build a Rule using the Lambda syntax inside RuleBuilder's addRule() method, the given is implicit and as mentioned above, the Result type is inferred.
Let's take another example.
Rule rule = RuleBuilder.create().withFactType(String.class)
.given(factMap) //assume a FactMap was already created and populated with some facts
.when(facts -> facts.getValue("first").equalsIgnoreClase("first fact"))
.using("first")
.then(System.out::println)
.then(facts -> facts.get("first").setValue("Altered First Fact"))
.build();
rule.invoke();
There are a few interesting things going on in the above example. The type of facts specified for the Rule in the RuleBuilder is String. That means every use of facts in the remainder of the Rule building will implicitly be String. But more than that, it means that the facts available for use in the Rule (in the when and then Lambda expressions) will be restricted to only facts of String type.
Let's assume that there are multiple String facts given to the Rule. The first then() statement prints a single fact since a FactMap with a single fact defers its toString() to that fact and a fact defers its toString() to its value object. So, a System.out::println method reference will print the value of the fact it contains, assuming that it contains only one fact. Of course, we know that the first then() statement only provides a single fact because the using() statement directly above it restricts its facts to a single fact named "first."