Logical insertion of objects binds the inserted objects with the condition that triggered their insertion. Consider:
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
IsLargeOrder object will be automatically removed. Due to this behavior, in most practical cases
insertLogical is the preferred way to manually insert new objects.
Handling deviations of our rules
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);
Deviations to our deviations
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:
- If you have an order over
150dollars, you consider it a large order
- In these orders, if they have less than five items, you consider them a large order
- If it’s less than five items, but over
300dollars (well over
150dollars), you also consider them a large order
For 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:
@Strict: This marks a rule that cannot be defeated. In this type of scenario, it is useful to mark rules that should not be overridden by any other.
@Defeasible: This marks a rule that can have deviations.
@Defeats: This annotation receives a list of specific rules it can defeat. It is the point where the rule independence gets broken as it has to know the name of other rules.
@Defeater: This marks a special case of defeats. It can defeat other rules, but the changes it makes won’t be propagated in the working memory. This means that the rules marked with
@Defeaterwon’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 // nothing to do 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