-
Notifications
You must be signed in to change notification settings - Fork 126
Rules
Rules are the basic building blocks of RuleBook. They are the individual conditions and actions that evaluate and act on facts, respectively. Rules contain facts and optionally a Result. And all Rules implement the com.deliveredtechnologies.rulebook.model.Rule interface.
RuleBook comes packaged with a single implementation of the Rule interface, GoldenRule. However, you are welcome to create your own implementation of the Rule interface and use that instead. But you shouldn't need to. GoldenRule should have all you need. The rest of the page will discuss Rules in the context of the functionality implemented in GoldenRule.
A Rule, by itself can accept facts, evaluate a condition based on those facts, execute one or more actions if that condition evaluates to true and (optionally) maintain a Result. The real magic comes when Rules are chained together in a RuleBook. However, this page limits the discussion to how a Rule itself works. If you want to know more about how Rules are executed in the context of a RuleBook, see the RuleBooks page in this Wiki.
A Rule can have a single condition, which is a Predicate functional interface. When that condition evaluates to true, each of the actions specified for the Rule are executed in the order in which they were specified. If no condition exists for a Rule, the condition is assumed to be true.
Rules are invoked using the invoke() method, which returns a boolean result. The invoke() method returns true if the actions of the Rule execute without error. Otherwise, the invoke() method will return false.
Since a Rule's condition is supplied by a Predicate functional interface, a common way to define the implementation of that interface is through Java's Lambda syntax. The type of the parameter used in the Predicate is NameValueReferableTypeConvertibleMap, which is really just NameValueReferableMap (impl: FactMap) with some type conversion convenience methods attached to it. Of course, as a Predicate, the return type is boolean.
An important thing to note about evaluating conditions is that facts supplied to the condition are filtered by the type of facts evaluated by the Rule. Below is an example that illustrates this point.
Rule<String, Object> rule = new GoldenRule<>(String.class);
rule.addFacts(new Fact("str", "String Value"), new Fact("bool", true));
rule.setCondition(facts -> facts.getValue("bool"));
rule.addAction(facts -> System.out.println(facts.getValue("str")));
rule.invoke(); //returns false; invocation of the condition will fail since "bool" fact is not available
In the above example, the action that prints the value of the "str" fact is not executed. The reason the action is not executed is because the type of the facts used in the Rule is String, but "bool" is type Boolean. And since Boolean does not match the type of facts used by the Rule, it is not available in the Rule's condition.
A Rule's actions are invoked upon the execution of the invoke() method in the order in which they are added to the Rule if and only if the Rule's condition evaluates to true. If any action throws an exception, if the condition throws an exception or if the condition does not evaluate to true then the invoke() method will return false. However, if the condition evaluates to true and if all actions execute successfully, then the invoke() method will return true.
Like a Rule's condition, the facts available to a Rule's action are filtered by the type of facts that are used by the Rule as shown in the following example.
Rule<String, Object> rule = new GoldenRule<>(String.class);
rule.addFacts(new Fact("str", "String Value"), new Fact("bool", true));
rule.addAction(System.out:println); //only the "str" fact is available because it's the only String fact
rule.invoke(); //returns true
Unlike a Rule's condition, actions can accept facts or facts and a result as shown in the example below.
Rule<Boolean, String> rule = new GoldenRule<>(String.class);
rule.addFacts(new Fact("str", "String Value"), new Fact("bool", true));
rule.setCondition(facts -> facts.getOne())
rule.addAction(System.out:println); //only the "bool" fact is available because it's the only Boolean fact
rule.addAction((facts, result) -> result.setResult("Completed rule actions!")); //the Result type is String!
if (rule.invoke()) { //should evaluate to true because all actions should execute
rule.getResult().ifPresent(System.out::println);
}
The above example should print the following.
true
Completed rule actions!
Facts are just information provided to Rules in the form of a name/value pair, where the name is a String value and the value is an object reference. Specifically, fact is an instance of NameValueReferable. Facts are represented this way so that a name always refers to a fact, regardless if the fact's object is mutable or not.
Facts can be added individually to Rules or passed as a group to a RuleBook, as a NameValueReferableMap. The included instance of NameValueReferableMap is appropriately named FactMap. A NameValueReferableMap is really just a Map of NameValueReferable objects, or facts. However, NameValueReferableMap has a few tricks up its sleeve to make it more convenient to work with.
For example, a simple Map of facts would require the following syntax to get a fact's value.
Object value = hashMapOfFacts.get("fact key").getValue();
However, if we use NameValueReferableMap, the following statement should work as its equivalent.
Object value = nameValueReferableMap.getValue("fact key");
Similarly, the setValue method can be used on a FactMap (which implements NameValueReferable) to set the value of the associated fact or create a new one with a name equivalent to the key value.
factMap.setValue("new fact", obj); //assume obj was some object that was previously created.
Remember from above that Rules can be restricted to use a specific fact type. Well, sometimes Rules need to use facts of more than one type. Consider the following example using the RuleBuilder (discussed in RuleBook's Java DSL).
Rule rule = RuleBuilder.create()
.when(facts -> facts.getIntVal("some value") > 10)
.using("some value")
.then(facts -> facts.setValue("message", "Over ten!"))
.using("message")
.then(System.out::println)
.build()
No specific fact type was set for the Rule above. That means facts of every type are available to the Rule. But it also means that if you want a fact to behave like a specific type, then you have to cast that fact... except in many cases you don't. Because the NameValueReferableMap object provided to the when() and then() methods of any Rule or RuleBuilder are decorated with NameValueReferableTypeConvertible. And NameValueReferableTypeConvertible provides convenience methods, like getIntVal() that do the conversions for you.