Rule attributes

Drools rules are data-driven. This means that the only way to activate a rule is by adding data to the engine that matches the conditions of that rule. However, there are multiple circumstances where we will want some rules with matching situations to be filtered out. One of this filtering mechanism is called rule attributes.

Rule attributes are extra features that we add to our rules to modify their behavior in a specific way. There are many rule attributes (of which, we'll explain the most used ones and some of their possible combinations) and each one modifies the way we filter rules execution in a different way, as follows:

rule "simple attribute example"
enabled false
   when Customer()
   then System.out.println("we have a customer");
end

If the enabled attribute is set to false, the rule will be evaluated, however, it won't be executed. It is perhaps the simplest example of a rule attribute shown here to see that the rule attributes are written before the when part of a rule. However, even if that's their syntactic position, they come second to the conditions of the rule. This means that if the conditions of the rule don't find a match for the working memory, attributes won't even play a part in the execution. However, when a rule set of conditions matches a group of objects in the working memory, rule attributes will come as an extra component to decide whether this match should fire now, later, or not at all.

The following subsections will show some of the most commonly used rule attributes and how they influence the execution of our rules is shown as follows

  • Deciding the rule matches that will fire and the rule matches that won't fire
  • Splitting our rules in groups, which might be valid or invalid in different situations
  • Controlling other special situations in which rules might or might not be valid

Example – controlling which rules will fire

When we define rules, the rule that matches the data with its conditions first will be the first in the list of rules to fire. This means that the order of the execution of rules is not deterministic. However, sometimes, we might need some rules to take precedence over the rest. For example, in our example about classifying items of rules, we saw in the previous chapter that we might have a specific subcategory in a special set of values between the mid range and we might want the cases where this rule finds a match to take precedence over the common mid-range classifying, as follows:

rule "Classify Item - Mid Range"
    when $i: Item( cost > 200 && cost < 500, category == Category.NA )
    then
        $i.setCategory(Item.Category.MID_RANGE);
        update($i);
end
rule "Classify Item - Mid/High Range (special)"
   when
      $i: Item( cost > 300 && cost < 400,category == Category.NA )
   then
      $i.setCategory(Item.Category.SPECIAL_MIDHIGH_RANGE);update($i);
end

In this example, if we add an item with the cost being 350, the first rule might be evaluated before the second one. If we want the second rule to take precedence, we can set a higher priority to it using the salience rule attribute. The higher the salience, the higher the priority of the rule is, as shown in the following:

rule "Classify Item - Mid/High Range (special)"
salience 10
   when
      $i: Item( cost > 300 && cost < 400 )
   then
      $i.setCategory(Item.Category.SPECIAL_MIDHIGH_RANGE);
      update($i);
end

By default, all rules have an implicit salience attribute of 0 and you can assign positive or negative values to the salience attribute in order to execute them before or after the rest of the rules. Please take into account that rule attributes will only be evaluated after the rule conditions have matched with a group of data in the working memory, therefore, if the rule was not going to be triggered with the existing data, it won't be triggered regardless of how high or low the salience value is set.

Tip

Note that in Drools 6, rules have an implicit relative salience by default that prioritizes the rules that appear earlier in the same DRL file. There's no relative implicit salience between rules in different DRL files though.

There is a catch in the rule that we rewrote here, we stopped checking for the category attribute being set to NA. We did this on purpose in order to explain a common problem when getting started with Drools rules. As you can see, the rule consequence is updating the item and setting a category for it. Once update is called, the object will be re-evaluated for all the rules, including this one. This means that the item will be re-evaluated for this rule and if it still matches its condition (the cost being between 300 and 400), it will trigger the rule multiple times.

This sort of infinite loops can be managed in different ways. In the earlier versions, we checked whether, in the condition of the rule, the category was still NA. Once we modified the category, updating the object would trigger a re-evaluation of the rule, however, as it no longer has an NA category, it wouldn't match the condition. This is the preferred way to do things when possible, however, if checking for a condition of this type becomes too complex, a rule attribute exists to let the engine know that a specific rule should not re-evaluate itself after it modifies the working memory. This attribute is the no-loop attribute, as shown in the following:

rule "Classify Item - Mid/High Range (special)"
no-loop
salience 10
   when
      $i: Item( cost > 300 && cost < 400 )then
      $i.setCategory(Item.Category.SPECIAL_MIDHIGH_RANGE);
      update($i);
end

You can test what happens when this attribute is removed in the RuleAttributesTest class of the chapter-04-tests project in the code bundle. The infinite loop is stopped through this attribute as it is this very rule that retriggers itself over and over again.

One other rule attribute that is very simple is the enabled rule attribute. It receives a boolean parameter to let the engine know whether the rule should be executed or not. If false, the rule is not evaluated, as follows:

rule "Classify Item - Mid/High Range (special)"
enabled true
no-loop
salience 10when$i: Item( cost > 300 && cost < 400 )then$i.setCategory(Item.Category.SPECIAL_MIDHIGH_RANGE);update($i);end

This might seem like a weird rule attribute. Why would we want to use a rule attribute to disable a rule? You could just comment it out or delete the rule. In order to understand why it exists, we need to understand that rule attributes, even if they are written before the conditions of a rule, are always evaluated after the conditions of a rule. This means that they can use the data from the condition to decide the boolean value of the enabled attribute, integer value of the salience attribute, or any other attribute value that we might define in the future.

Given this information, we're going to rewrite our rule with two different uses of variable values for rule attributes, we're going to set the salience value based on the item's cost from the condition and we're going to set whether the rule is enabled or not based on a boolean method from a global variable, as follows:

global EShopConfigService configService;
...
rule "Classify Item - Mid/High Range (special)"
no-loop
salience ($i.getCost())
enabled(configService.isMidHighCategoryEnabled())
   when
      $i: Item( cost > 300 && cost < 400 )
   then
      $i.setCategory(Item.Category.SPECIAL_MIDHIGH_RANGE);
      update($i);
end

As you can see in the previous example, we're defining the salience of the rule based on a variable numeric value (specifically, the cost of the item detected in the condition), and we're setting the enabled attribute based on the return value of a boolean method in the global variable. As long as the condition is written in parentheses and Java code, the engine is capable of understanding them.

Example – splitting rule groups with agenda group

Now that we've understood the structure of rule attributes and played a bit with the simplest three attributes that are available, it is time to continue stepping up our rule game by going into even more complex rule attributes. The next set that we're going to see are the ones used to define groupings of rules.

Rules should not be microcontrolled in such a way that we say exactly which rule will fire next, however, this doesn't mean that we should let the engine run every single rule we define all at the same time. Even if we create a moderate-size rule-based project, we are going to see our rules fall in different categories. In our eShop example, some rules are going to be for data input validation, some are going to validate promotions that apply to the existing purchases, some will apply different taxes to our purchase invoice, and so on and so forth. Each rule makes sense to be applied at a different time. Drools provides grouping mechanisms for our rules to be able to activate a group of rules each time. These groups are also defined through rule attributes.

It might seem against the declarative approach, however, it is still controlled by the data fed to our rule engine. The fundamental difference between trying to control one rule against a group of rules is that the group is still managed by the rule engine. The declarative approach still applies, but only for a subset of all the rules that we defined.

The most used type of grouping for rules is defined with the agenda-group rule attribute. This rule attribute defines a key, which can be activated by a code in the KieSession and changed as required as many times as it makes sense in our case, as shown in the following:

rule "Promotion: more than 10 pencils get 10% discount"agenda-group "promotions"
    when
        OrderLine(item.name == "pencil", quantity > 10)
    then
        insert(new Discount(0.10));
end

The previous rule defines a rule under the "promotions" grouping. This particular group should have all the rules that involve applying promotions to a shopping cart and it will still be the rule engine's job to determine the rule that should be fired next, if any.

The agenda group will be manually activated through an API call to the KieSession object (before calling fireAllRules), as follows:

KieSession ksession = …;
   ksession.getAgenda().getAgendaGroup("promotions").setFocus();
   ksession.fireAllRules();

It's worth mentioning here that, by default, all the rules have an implicit MAIN agenda group. The KieSession has the group active by default and all rules that don't define an agenda-group attribute fall into this group.

Also, every rule, whether it is in the active agenda group or not, will be evaluated when rule evaluations get triggered. The active agenda group will determine the group of rule matches that should be executed at rule-execution time.

It's also worth mentioning that, when a rule is fired, it can also define the agenda group that is going to be activated through the implicit global variable called kcontext, as follows:

rule "Done with promotions. Onward with printing invoice"
    salience -100 //last rule of group to run
    agenda-group "promotions"
    when
        OrderLine()
    then
        kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup("MAIN").setFocus();
end

Take a good look at the previous rule. There are many tricks placed together in there. First of all, you have a negative value in the salience attribute. This means that this rule will take a very low priority so that even if it is activated, as long as there is another rule match, it will take precedence to this one. This makes this rule the last rule of the group likely to run. The condition asks for an OrderLine object, therefore, as long as we have an order line and the rules have done everything they need with it, we'll execute this rule's action.

As the consequence of this rule, the kcontext default variable is used to access the KieSession, using the getKnowledgeRuntime method. Through this, it can activate the next agenda group just like it did on the plain Java code. You can see that the agenda group activated is the MAIN agenda group, which is the default agenda group so that the next set of rules to be matched and possibly fired is the group that didn't define an agenda group.

Other types of rule groups

Agenda groups are very useful to not only define a specific sequence in our rules, but also let the rules determine the next group of rules that is to be activated. Very complex situations where rules have to control, the next set of actions to follow can be represented using these types of rules. However, there are times when rules are exposed through user-friendly editors to business users and they usually want a more graphical way of defining rule group sequences.

There is a tool for Business Process Management (BPM) called jBPM, which uses Drools as its base API and rule engine. It allows the end users to define diagrams to show the sequence of steps in a process. Some of these steps can be rule executions and to determine the rules that should fire at that particular point, they use a common attribute between the rule step in the process and the rules that are going to be invoked: the ruleflow-group rule attribute.

Ruleflow groups are used for exclusive interaction with business processes and there isn't an exposed API to invoke the ruleflow group that should be activated. Instead, this is done directly by the runtime of jBPM.

Groupings are used to split rules in groups; however, sometimes, we need these groups to have an even more specific behavior. For example, we might define that a specific group of rules should be mutually exclusive. To define such behavior, Drools defines a rule attribute called activation-group, which defines that only one rule should fire in that group. If the data that we feed the KieSession matches five rules in the same activation group, the first one to fire will cancel the other four rules.

Note

Note that rule attributes can only be defined once per rule, however, you can have multiple rule attributes defined in a single rule. The combinations of rule attributes is a very powerful tool once we understand the purpose of all the attribute types.

Rule dates management

There are times when we want our rules to be considered only in specific moments in time. Some rules, specially company policies, such as special discounts for retail, tax calculations, and holiday specials, make sense only on specific dates or days of the week. There are rule attributes with this very purpose and they allow you to control whether or not a rule is enabled at a specific point in time.

Two of these attributes, date-effective and date-expires, determine the start and end date for a specific rule to be enabled. If we assume the government establishes a specific tax to be added to every purchase from 2015 to 2020, this would be a good way to define this rule, as follows:

rule "Add special tax of 3%"
   date-effective "01-Jan-2015"
   date-expires "31-Dec-2020"
   when $i: Item()
   then $i.setSalePrice($i.getSalePrice() * 1.03);
end

The dd-mmm-yyyy date format is supported by default. You can customize this by providing an alternative date format mask as a drools.dateformat system property.

There is another type of common case, where we might need to switch the rule from enabled to disabled periodically or based on a specific date configuration. In our eShop example, this could be related to promotions such as 2 x 1 on the purchase of beers on Saturdays. This rule should not apply to any other day, therefore, we use a special attribute called calendars to specify the days when a rule is enabled.

Also, we might need to retrigger a specific rule on a specific schedule as long as the condition of the rule continues to match the specific data. For this kind of situation, there is a timer rule attribute that allows you to set either cron or interval-based timers to your rules.

Here's an example of these two types of attributes working together on some rules:

rule "Weekday notifications of pending orders"
   calendars "weekdays"
   timer (int:0 1h)
   when Order($id: orderId)
   then emailService.sendEmail("Pending order: "+$id);
end
rule "Weekend notifications of pending orders"calendars "weekends"
   timer (cron:0 0 0/8 * * ?)
   when Order($id: orderId)
   then emailService.sendEmail("Pending order: "+$id);
end

As you can see, these two rules do pretty much the same. If there is an order on the working memory, they send an e-mail through a helper class set as a global variable called emailService. The main difference between the two rules is provided by the rule attributes. The first rule will only be active on the days the weekdays calendar tells it to be, while the second rule will only be active on the weekends calendar. Also, each rule will fire at different rates as long as the condition is still fulfilled, the first rule will fire at one hour intervals (with zero time of delay for the first time) and the second rule will fire exactly at 00:00, 08:00, and 16:00 hours. They both require that the rule execution is invoked so that fireAllRules should be called continuously in order to get the firing rate going.

The calendars have to be configured through the KIESession using the getCalendars method, as follows:

ksession.getCalendars().set("weekday", new Calendar() {
    //for simplicity, any date/time matches this calendar
    public void isTimeIncluded(long timestamp) {
        return true;
    }
});

You can find an example of its configuration in the chapter's code bundle, under the name TimersAndCalendarsTest. Any object that matches the org.kie.api.time.Calendar interface can define any form of calendar with any kind of business logic behind it.

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

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