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));

Then, if at some point in the future, the order changes its total to less than 150, the 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");

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 150 dollars, 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 300 dollars (well over 150 dollars), 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 @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));

rule "large orders exception" @Defeats("large orders")
    when Order($id:orderId, total>150.00, totalItems < 5)
    then // nothing to do

rule "large orders double exception"
  @Defeats("large orders exception")
    when Order($id:orderId, total>300.00)
    then insertLogical(new IsLargeOrder($id));

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.