Book Image

Instant Drools Starter

By : Jeremy Ary
Book Image

Instant Drools Starter

By: Jeremy Ary

Overview of this book

<p>Drools is a popular business rule management system. The book introduces the concept of rules separation, from what to do to how to do it. This Starter guide supports your development to keep pace with your system’s ever-changing needs, making things simple and easy by taking the rigidity out of complex codes.<br /><br />"Instant Drools Starter" is a practical, hand-on guide that provides you with a number of clear and concise explanations with relatable examples. This book offers step-by-step exercises to help you understand business rule management systems. Learn how they work, how they're best used, and how to perform frequently used tasks and techniques.<br /><br />This Starter guide helps you get familiar with the Drools concept. You will learn to evaluate rules engines and cover all the basics, from rules authoring to troubleshooting. This book highlights the capabilities of the Drools modules.</p> <p><br />After discovering exactly what rules are and what a rule engine brings to the table, we will quickly learn how to install Drools. The guide then explores different tools of the trade and gets you writing your first set of rules instantly. We'll then take those rules, write vital codes piece by piece, and put them into action. In addition, with this guide, learn how to document and troubleshoot everything behind the scenes, as well as developing your rules to the next level. "Instant Drools Starter" will cover everything you need to know to get started, so if you are looking for a complete guide that provides simple solutions to complex problems, look no further.</p>
Table of Contents (7 chapters)

Quick start – creating your first rules application


For the purposes of this book, we'll continue using Apache Maven for project structure management, given that Drools already relies on this tool and learning a bit about it may assist you in navigating the Drools source code. However, we'll begin with creating a new project with Eclipse and the Drools plugin. While this will not provide you with a Maven-enabled project or reflect what you'll typically find in an enterprise development structure, it does provide you with the basics needed to start tinkering with rules right away.

Step 1 – creating a Drools project with the Eclipse plugin

First things first, let's start a new project using the plugin's Drools Project Wizard to get things up and going.

  1. Select Project... by navigating to File | New.

  2. Expand the Drools folder and select Drools Project, then hit Next.

  3. Give your new project a name such as TacoShop, and hit Next.

  4. In this screen, the default settings are fine, but I wanted to point out a few things the wizard is capable of that we're not going to use. Leave the top two boxes checked only, but take note of the other options. Hit Next.

  5. Since this is the first time you've created a Drools project, you're unlikely to have a Drools runtime environment created, as reflected by the warning, so let's do that now. Click on the blue link named Configure Workspace Settings....

  6. At this screen, click on Add, and then on the Create a new Drools 5 Runtime... button.

  7. Specify a location for the plugin to place your JAR files.

  8. Back at the runtime screen, place a check next to the newly created Drools runtime location and hit OK.

  9. Now click on Finish and let the plugin do its thing.

Congratulations! You've now got a working rules application. If you're in a hurry to see what DRL, or the Drools Rule Language, looks like, you can navigate to Sample.drl and have a look. You'll find the executable portion of code inside the file called DroolsTest.java. You can run it and see the output in your console by right-clicking the file name and selecting Run As | Java Application.

Checking your console, you should now see two lines of output. The first reads Hello World, and the second reads Goodbye cruel world. If you're seeing both, then it's a success! You've just set up and executed your first rules-integrated application.

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Step 2 – creating a new rules-enabled Maven project from scratch

The creation wizard cranked out a lot of code for us, and if we were to just continue on with this project, we'd be omitting explanation of the most vital parts of the code, so let's build up our own project piece by piece and find out what makes it tick.

We'll be creating a simple rules system that will aid us in rewarding customers of our fictional company, Tucker's Taco Shop, for large purchases and combo deals.

In a new or existing Maven project, let's add the required drools dependencies. I've created a new project called DiscountProgram. Your pom.xml file should look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="maven.apache.org/POM/4.0.0"
         xmlns:xsi="www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="maven.apache.org/POM/4.0.0 maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    	<groupId>com.tacoshop</groupId>
  		<artifactId>DiscountProgram</artifactId>
  	<version>0.0.1-SNAPSHOT</version>
    
	<properties>
        <droolsVersion>5.5.0.Final</droolsVersion>
    </properties>

    <dependencies>
        
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${droolsVersion}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
</project>

So what have we done here? We've specified some information about our project, mainly the groupId, artifactId, and version number—all things that Maven uses to identify your application. Secondly, we've added a property called droolsVersion to keep life simple so that we can't accidentally forget to change one of the Drools component version numbers in the future. Next, we've included the three main JAR files we'll need to utilize the Drools rules engine. The first is the drools-compiler dependency. It's worth mentioning here that by including drools-compiler, we've pulled two transitive dependencies worthy of noting, knowledge-api and drools-core. When the time comes to start digging into drools code, you'll see these API names again. Lastly, we've included commons-lang, another required dependency, and junit at the test scope level, to provide some mechanism to run our rules.

Step 3 – defining our rules and facts

Before we go much further, let's stop and think up a basic set of rules we'd like to include in our discount program. Let's allow customers a discount of 10 percent on purchases over $15. Let's also give 15 percent off for purchases over $25. Finally, we'll allow a combo deal discount of an additional 5 percent off for purchases containing two or more tacos and at least one drink. Remember, rules tend to follow the format of When...then, so let's word them as such:

  • When a customer makes a purchase greater than $15, then provide a 10 percent discount

  • When a customer makes a purchase greater than $25, then provide a 15 percent discount

  • When a customer makes a purchase containing two or more tacos and at least one drink, then provide an additional 5 percent discount

Now that we know what rules we'll have, let's figure out what facts we need to know about. Since we're intentionally sticking to a simple rule set, we only need to know about one thing, the purchase; we need to know how much it costs, what it consisted of, and the discount total.

Step 4 – translating facts into a data model

With a fact model in mind, we need to create a Plain Old Java Object (POJO) class to represent our model.

  1. Create a new class which will represent the Purchase object.

  2. Add properties to our class for each trait that we've deemed important to us.

  3. Add getters and setters for said properties.

    package com.tacoshop;
    
    import java.math.BigDecimal;
    
    public class Purchase {
    
      private BigDecimal total;
      
      private int tacoCount;
      
      private boolean drinkIncluded;
    
      private double discount;
    
      public Purchase(BigDecimal total, int tacoCount, boolean drinkIncluded) {
        this.total = total;
        this.tacoCount = tacoCount;
        this.drinkIncluded = drinkIncluded;
        this.discount = 0;
      }
    
      public BigDecimal getTotal() {
        return total;
      }
    
      public void setTotal(BigDecimal total) {
        this.total = total;
      }
    
      public int getTacoCount() {
        return tacoCount;
      }
    
      public void setTacoCount(int tacoCount) {
        this.tacoCount = tacoCount;
      }
    
      public boolean isDrinkIncluded() {
        return drinkIncluded;
      }
    
      public void setDrinkIncluded(boolean drinkIncluded) {
        this.drinkIncluded = drinkIncluded;
      }
    
      public double getDiscount() {
        return discount;
      }
    
      public void setDiscount(double discount) {
        this.discount = discount;
      }
    }

Nothing too out of the ordinary in there. We've got a simple POJO with a constructor, some properties, and their getters and setters. Moving along!

Step 5 – translating rules into Drools Rule Language

So we've established a data model and we've already defined what rules we need, so now let's build our DRL file.

  1. Create a new file in src/main/rules called discountRules.drl. I've used a package called com.tacoshop.rules inside my rules file.

  2. If you're using the Eclipse plugin, you can take advantage of their Rule Resource wizard by navigating to New | Other... | Drools | Rule Resource.

  3. Add the following to your new rules file and save it:

    package com.tacoshop.rules
    
    import com.tacoshop.Purchase
    
    rule "purchase over 15 and less than 25 dollars"
    when
      $p : Purchase ( total > 15, total <= 25 ) 
    then
      $p.setDiscount(0.10);
      System.out.println("Level 1 discount: " + $p.getDiscount());
    end
    
    rule "purchase over 25 dollars"
    when
      $p : Purchase ( total > 25 )
    then
      $p.setDiscount(0.15);
      System.out.println("Level 2 discount: " + $p.getDiscount());
    end
    
    rule "purchase contains combo" salience -1
    when
      $p : Purchase ( drinkIncluded, tacoCount >= 2 )
    then
      $p.setDiscount($p.getDiscount() + 0.05);
      System.out.println("Combo discount: " + $p.getDiscount());
    end

Now let's break it down...hey, stop dancing, that's not what I meant. The rules! The package and import statements, as seen here at the top of the file, are structured the same way as they are in Java class files. The rules, however, aren't so familiar to us. Notice that each starts with a rule name, and follows the format of when ... then ... end. We've discussed something similar to that construct once or twice by now, right? It's all coming together.

Our rule conditions (the when bit) use a dialect called MVFLEX Expression Language (MVEL), which makes them seem a little foreign to us. For one, we're not calling getters to access properties. MVEL takes care of that for us. It helps to read each condition aloud to yourself:

  $p : Purchase ( total > 25 )

The previous code can be read aloud as "There exists some purchase having a total greater than 25." In the case that such a purchase is found, we set it to a variable, $p. In the consequences of our rules, we take some action desired when our condition is met. Notice the consequences in the rules look like Java, but also reference the $p variable set from the conditions. In our first rule consequence, we set a discount of 10 percent to $p and we print something to the console showing us that the discount has been added. The rest of the rules follow the same format. Feel free to take a moment to look them over and read them aloud. The last thing I should mention before we move on is the keyword seen on the combo rule, salience. This keyword sets priority to a rule. By default, all rules have a salience of zero. You can specify a negative or positive salience, so in our case, we've used a negative one. Why? Because we want our combo discount to be in addition to any other discounts added to the order, so we need to give it a lower priority to ensure it's done last.

Step 6 – doing something with our rules

Take a quick break, maybe grab some coffee (try not to think about coffee rules, it's a break). This is going to be a big one. I'll wait here.

Moving on! So now that we have a data model and a proper rule resource, we can get to executing our rules. We're going to create a JUnit test that will compile our rules, build a session, insert some facts, and fire our rules. Let's get started by taking a look at the whole test, then we can examine the contents:

@Test
  public void testRules() {
    
    StatefulKnowledgeSession session = null;
    
    try {
      KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
      builder.add(ResourceFactory.newClassPathResource("discountRules.drl"), ResourceType.DRL);
      if (builder.hasErrors()) {
          throw new RuntimeException(builder.getErrors().toString());
      }
  
      KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
      knowledgeBase.addKnowledgePackages(builder.getKnowledgePackages());
      
      session = knowledgeBase.newStatefulKnowledgeSession();
  
      // purchase > $15 and <= $25
        Purchase firstPurchase = new Purchase(new BigDecimal("16"), 1, false);
      FactHandle purchaseFact = session.insert(firstPurchase);
          session.fireAllRules();
          
    } catch(Throwable t) {
       t.printStackTrace();
     } finally {
      if (session != null) {
          session.dispose();
        }
     }
  }

Let's start at the top! We've made a standard JUnit test (for now, let's ignore the fact that we're not really asserting anything inside our test—we'll get to that later):

@Test
  public void testRules() {

It's good practice to wrap your rules code in a try/catch block. For that reason, we've extracted a variable declaration needed within the finally block to outside of the try block:

    StatefulKnowledgeSession session = null;

Let's talk a bit about what StatefulKnowledgeSession is. Our session is the main means of communication with the rules engine. It provides us with some handy methods for inserting, updating, and retracting our facts. It also gives us methods for setting global variables (more on these later), as well as a method I like to refer to as the Big Red Button, fireAllRules().

Whenever you work with a session, it's important to always remember to use the dispose() function to dispose of it when you're done. If you don't, the garbage collector won't be able to free up the resources for you, and that means problems. The purpose of our finally block is to ensure that won't be an issue. So how do we build a session? Let's start with KnowledgeBuilder:

 try {
   KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
   builder.add(ResourceFactory.newClassPathResource("discountRules.drl"), ResourceType.DRL);
   if (builder.hasErrors()) {
    throw new RuntimeException(builder.getErrors().toString());
   }

What's happening here is the compilation of our rules resource. The KnowledgeBuilder is responsible for taking resources and compiling them into rule packages. If we have any syntax or other compilation errors within our rules (notice I didn't say logic!), now's the time that they'd show up. Once it's built, we'll need to do something with our nifty rule package. Moving on to KnowledgeBase:

 KnowledgeBase knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
 knowledgeBase.addKnowledgePackages(builder.getKnowledgePackages());

Notice that KnowledgeBase accepts the packages created by our builder. The KnowledgeBase can be thought of as a manager for our collection of knowledge packages, or as I've been referring to it, rule packages. It primarily provides us with an interface for inserting and removing packages, as well as a method for creating sessions populated with our rule information:

 session = knowledgeBase.newStatefulKnowledgeSession();

So now we have a rule session loaded up with our logic. Next it's time for the data. Let's throw something in so we can see some results:

 // purchase > $15 and <= $25    
 Purchase firstPurchase = new Purchase(new BigDecimal("16"), 1, false);
 FactHandle purchaseFact = session.insert(firstPurchase);
 session.fireAllRules();

We've initialized a Purchase variable with an amount of $16, one taco, and no drink (man, that's an expensive taco!). If we refer back to our rules, we'll see that this should be enough to trigger our first rule. Notice the fireAllRules() function in there? We're executing now and should see some output from our rule consequence. Success! The console confirms that a discount of 10 percent has been added to our purchase:

Level 1 discount: 0.1

So our rules have done their thing and we're ready to close up shop. We finish up with a catch block that will relay any issues encountered, as well as dispose of the session:

 } catch(Throwable t) {
    t.printStackTrace();
 } finally {
    if (session != null) {
       session.dispose();
    }
 }

What if we wanted to trigger our other rules as well? Let's add a few more lines to our test after the first call to fireAllRules() to flex the other two rules as well. You may have noticed we set something called a FactHandle at the same time we inserted a Purchase fact. We'll use that to update our rule session once we've made some changes to the Purchase object. Add these lines immediately after fireAllRules() and rerun the test:

 System.out.println("----------------");
          
 // purchase > $25
 firstPurchase = new Purchase(new BigDecimal("26"), 1, false);
 session.update(purchaseFact, firstPurchase);
 session.fireAllRules();
 System.out.println("----------------");
          
 // combo purchase containing 3 tacos and a drink
 firstPurchase = new Purchase(new BigDecimal("26"), 3, true);
 session.update(purchaseFact, firstPurchase);
 session.fireAllRules();

Level 1 discount: 0.1
----------------
Level 2 discount: 0.15
----------------
Level 2 discount: 0.15
Combo discount: 0.2

Congratulations! You're now working with Drools rules. If you'd like to download the accompanying code for this book (which includes better format and comments!) you can find it at http://www.packtpub.com/.