Let’s create a rule categorizing customers by the size of the order that they make. The rule will need to evaluate orders and customers:

 
rule "Classify Customer by order size"
    when
        $o: Order( orderLines.size >= 5, $customer: customer )
        $c: Customer(this == $customer, category == Customer.Category.NA)
    then
        modify($c){
       	    setCategory(Customer.Category.SILVER)
        };
end

In this rule, we are evaluating the orders with more than five order lines, which means five different items. Then, we look for the customer associated to this order and set this customer category. The relationship between the customer and order is achieved by binding the customer reference in the Order object to a variable called $customer and comparing Customer that we are evaluating against that reference by doing the following: Customer(this == $customer…).The order of the conditional elements is only defined by the bindings that we need. In this case, we are picking Order.getCustomer() to match the customer fact. However, we can do it the other way around as well and it will work in the same way, as shown in the following:

  $c: Customer(category == Customer.Category.NA)
  $o: Order( orderLines.size >= 5, customer == $c )

An important thing to understand at this point is that Customer() and Order() need to be facts, in other words, they need to be “root” objects in the Engine’s memory (e.g. as Kinesis data events or inserted using the insertLogical() method). While Order.getCustomer() is not a fact, it is an object in a fact.

For this rule to evaluate true, we need an Order and Customer object instances that make all these conditions true. Between the Order(...) and Customer(...) filters, there is an implicit AND, therefore, the rule can be read: When there is an order with more than 20 items AND a customer that is associated to that order, Then …. This is also equivalent and valid in the DRL language, as follows:

  $o: Order( orderLines.size >= 5, $customer: customer )
  and
  $c: Customer(this == $customer, category == Customer.Category.NA)

You may also have noticed the modify($c) sentence on the right-hand side of the rule. This modify() method is another operation provided by the Rule Engine to make sure that the engine knows that a fact has been changed and the change needs to be notified to other rules that might be looking to match these changes. In this case, we are letting the engine know about the modification of the category of the customer. If you omit modify($c), no other rule will know about the change in the category, which means that rules that depend on already categorized items will not be matched.

Now that our rules have become more complex, it is important to notice the fact that we are clearly separating the business definition from our application code. We are extracting the definition of how to categorize customers to these rules and we will be able to update this definition if the business definition changes without modifying the rest of the application. This is one of the core concepts of using business rules.

Now, based on this categorization, we can create different types of coupons for different customers, allowing us to treat each of our customers differently, based on their loyalty and previous orders:

coupons-creation.drl:
rule "Create Coupons for Silver Customers"
    when
        $o: Order( $customer: customer )
        $c: Customer(this == $customer, category == Category.SILVER)
    then
        insertLogical(new Coupon($c, $o, Coupon.CouponType.POINTS));        
end

Like the previous example, here, the rule is filtering by Orders and Customers; however, as you can see in the rule RHS, we are creating a new Object of the Coupon type and making it available to the Rule Engine using the insertLogical() method. This means that as soon as this rule gets executed by the Rule Engine, it will trigger any other rule that is expecting Coupons. Here the things become a little bit more interesting. We saw how the rules can generate new data and chain different rules together as soon as we make the new data available to the Rule Engine.

Now let’s make things a little bit more complex, let’s imagine that we want to check an order with two or more items that only contain HIGH_RANGE items and we want to apply some discounts to these specific orders.

In order to write a rule that check for this situation, we will also need to evaluate the OrderLine objects (we will need to add this import as well). This can be translated to adding more filters to our rules. Now, we will need to put constraints on the Order object, OrderLines and Item associated. The following UML diagram shows the relationships among these objects:

BinaryFile

The following rule expresses the previously introduced rule for discounts:

rule "High Range Order - 10% Discount"
    when
        $o: Order( $lines : orderLines.size >= 2, discount == null )
        forall( OrderLine( this memberOf $lines,  $item : item)
                Item(this == $item, category == Item.Category.HIGH_RANGE)
        )
    then
        
modify($o){
     setDiscount(new Discount(10.0))
};
end

We have three different object filters for Order(), OrderLine(), and Item(). Notice that this rule also depends on having our items classified; however, there is no explicit relationship between this rule and our first rule that categorize our items. One new thing introduced by this rule is the conditional element forall, which makes sure that all OrderLines and the associated items of the order are categorized as HIGH_RANGE items. If there is at least one item with a different category set associated with the current order, this rule will not get activated and fired. In the same way as earlier, we are updating the order so that if another rule is looking at the discount applied, the information is available to the engine.