Logical insertion of elements

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.

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

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.106.233