As we've discussed earlier, we should strive to keep our rules simple. To do so, sometimes, we break down a rule into multiple rules, making insertions of new data in the engine to trigger other simple rules. This helps in keeping the rules manageable as simpler rules will be easily understood. Here's a small example of how we can do such a thing:
rule "determine large orders" when $o: Order(total > 150) then insert(new IsLargeOrder($o)); end
In this way, we won't have to define what we consider a large order more than once. If we want to change this consideration in the future to, let's say, a total larger than 200, we will only have to change it once.
The one consideration that we need to have with this approach is that if the condition that triggered the insertion of IsLargeOrder
might stop being true in the future. If some rule or a piece of code changes the order to have a smaller total, the IsLargeOrder
object would still be in the working memory. We can avoid this by creating a rule to sanitize the working memory when the condition is not true anymore, but rules helps in avoiding this unnecessary rule duplication using logical insertion.
Logical insertion of objects binds the inserted objects with the condition that triggered their insertion. This means that if we rewrite the previous rule as follows:
rule "determine large orders" when $o: Order(total > 150) then insertLogical(new IsLargeOrder($o)); end
Then, if at some point in the future, the order changes its total to less than 150
, the IsLargeOrder
object will be automatically removed from the working memory.
Logical insertion not only avoid needing extra rules to sanitize our working memory, but also open the possibility of locking objects to specific conditions. This is very powerful because if we bind some form of negation
of object to a condition, we can define the deviations or exceptions to our inferences.
Binding a negation of an object is simple. Just use the insertLogical
keyword with a second parameter with the neg
string on it. Let's see the following example, where we will add an exception for what we consider a large order, the total items being less than five, regardless of the price:
rule "large orders exception" when $o: Order(total > 150, totalItems < 5) then insertLogical(new IsLargeOrder($o), "neg"); end
If we take the previous two rules, we will have one IsLargeOrder
object in the working memory for every order that has a total greater than 150
and more than five items. If, at some point, the total of an order decreases below 150
, the corresponding IsLargeOrder
object will automatically be deleted. If an order with a total above 150 and only four items gets another item, a corresponding IsLargeOrder
object will automatically be inserted.
This deviation management has the advantage of keeping the rules independent of each other. The deviation rules don't need to understand how many rules are adding an IsLargeOrder
object to the working memory, but only the situation where the object should not be added.
Note that the logical insertion can be done also for creation of traits. The don
keyword has a third optional boolean parameter and if you set it to true, the trait gets logically inserted in the working memory and only exists in it while the rule is evaluated to true, as follows:
don($traitObj, SomeTrait.class, true);
The previous approach allows us to have independent rules, but it doesn't let us add more than one level of deviations. If, at some point, we want to nest deviations (which means to add a deviation to an existing deviation), the previous syntax won't be enough. Let's first discuss an example of this double deviation situation:
150
dollars, you consider it a large order300
dollars (well over 150
dollars), you also consider them a large orderFor these cases, Drools provides a set of annotations that allows us to implement deviation trees in our rules. This method of writing rules, however, comes with a disadvantage. Using these annotations will break the rule independence as we have to specify that to which rules we are providing a deviation or else you might find yourself having rules and deviations to deviations competing with each other and possibly lead to the rules getting executed more than designed.
Nevertheless, we might still require to do a case involving deviations to deviations and this strategy manages the situation quite nicely. The set of provided annotations mark the rules to identify which of them are deviations and which of them can or cannot have them. These annotations are as follows:
@Defeater
won't trigger other rules. In very complex scenarios, this can be useful to stop deviation chains.Each of the rules should use insertLogical
to bind their inferences to the rule engine. Let's see the following example of the previous double deviation case implemented in Drools:
rule "large orders" @Defeasible when Order($id: orderId, total>150.00) then insertLogical(new IsLargeOrder($id)); end rule "large orders exception" @Defeats("large orders") when Order($id:orderId, total>150.00, totalItems < 5) then insertLogical(new IsLargeOrder($id), "neg" ); end rule "large orders double exception" @Defeats("large orders exception") when Order($id:orderId, total>300.00) then insertLogical(new IsLargeOrder($id)); end
In the previous set of rules, we first check for orders of more than 150 dollars and mark everything we find as a large order. The second rule establishes an exception, stating orders with less than five items are not large orders. The third rule, an exception to the second case, establishes that orders with less than five items are considered large as long as the total is over 300 dollars.
You will also need to specify in the kmodule.xml configuration that the KieSession will use defeasible logic. To do so, define your kbase and ksession tags as follows:
<kbase name="ruleExceptionsKbase" equalsBehavior="equality" packages="chapter04.ruleExceptions"> <ksession name="ruleExceptionsKsession"beliefSystem="defeasible"/> </kbase>
You can run this example in the RuleExceptionsTest
class in the code base.
3.138.106.233