Curtail complexity with a rules engine

Complexity can manifest itself within a software application in a number of hip ways, including dependency management (i.e. 3rd party libraries required for runtime, etc), architectural adherence patterns (think old style EJBs), and even coding constructs (in particular, excessive use of conditionals). When it comes to coding constructs, the resulting complexity is often related to the problem being solved. For example, imagine a recommendation wizard for sales associates selling hip disco LPs. Quite simple, right? You have two groovy choices– anything from Donna Summer or the Bee Gees. If only life were this easy, eh?

Now imagine a recommendation wizard for smokin’ sales associates trying to move beer. Now that’s more real, isn’t it? Imagine the store is trying to move (i.e. sell to customers) seven different types of beers– all varying in taste and characteristics. The store wants to develop an application that walks someone through a series of questions and based upon their answers, will recommend one of seven beers. Think of this application as an expert beer system– and while it may start with only seven beers, over time more beers will be added, especially if the system proves itself to move beers efficiently. What’s more, you the developer aren’t a beer aficionado (i.e. a domain expert on beers)– your job is to make an application that beer experts can modify so customers can pick beers more easily.

Logically, you can build a hip beer expert system with a couple of conditionals– if you like this characteristic, then you should buy this beer, right? In pseudocode, your logic could look like this (after you’ve had a beer session with the beer experts):

Do you like a light beer or a dark beer?
 if light beer:
  Do you like crisp, smooth beers or more prefer a more hoppy one?
   if crisp:
    then Pils
   else:
    Do you like light hops or more aggressive hops?
      if light:
       then Pale Ale
      else:
       IPA
else:
  Do you like the taste of coffee?
   if yes:
     Chocolate Stout
   else:
     Do you like spiciness?
       if yes:
         try Winter Ale
       else:
          Do you like high alcohol content?
            if yes:
              try an Eisbock
            else:
              try a Lager

This particular block of code (which enables one to pick one of seven beers and is by no means an accurate expert system), if isolated in a method, would have a cyclomatic complexity of at least 13, which presents a challenge– methods over 10, with conditional nesting, are havens for defects, especially if this code changes often. What if next week, the Pilsner brand is sold out? You’ll need to modify the logic to select perhaps another type of beer. In fact, the logic may not be as easy as replacing the Pilsner with another neat-o beer– it may involve a new series of questions.

It turns out that in these scenarios, a rules engine may actually be beneficial– in fact, rules engines (or expert systems) are well suited to replace excessive if, else, switch logic, especially if that logic is the domain of non-technical experts (in the case above, the beer experts haven’t a clue about coding nor hygiene, for that matter).

Using a rules engine, however, requires you to flatten business logic somewhat; in fact, in the copasetic beer expert system above, it requires you to focus on particular goals (i.e. moving a particular beer brand) and work backwards from that. For example, if I want to move an IPA, the attributes are:

  • Likes a light beer as opposed to a dark one
  • Prefers a hoppy taste
    • And tends to like a more aggressively hopped one too

Keep in mind, that in a real expert system for making recommendations, the number of attributes would most likely be greater. Based on the attributes of beer elaborated in the pseudocode above, however, I can group them into three categories, which I’ll designate as Java 5 enums:

public enum Color {
  LIGHT, DARK
}
public enum Taste {
  CRISP, HOPPY, AGGRESSIVE_HOPS,
  LIGHT_HOPS, COFFEE, SPICY, MALTY
}
public enum ABV {
  HIGH_ALCOHOL, NORMAL_ALCOHOL,
  LIGHT_ALCOHOL, NO_ALCOHOL
}

These enumerations will live inside of a BeerPreference object holds a Color, a Collection of Testes, and an ABV:

public class BeerPreference {
 private Color color;
 private Collection  tastes;
 private ABV abv;
 //...
}

The class will also hold a recommendedBeer property, which the rules engine will appropriately set based upon the other attributes’ values:

private String recommendedBeer;

public String getRecommendedBeer() {
  return recommendedBeer;
}
public void setRecommendedBeer(String recommendedBeer) {
 this.recommendedBeer = recommendedBeer;
} 

In my case, I’ll use Drools, which is an excellent open source expert system, to define my rules. For example, below is a tripped out rule for determining if the choices present mean a person should try out an IPA.

rule "Mendocino White Hawk IPA Rule"
 when
   $beer: BeerPreference(color == Color.LIGHT,
   tastes contains Taste.HOPPY,
   tastes contains Taste.AGGRESSIVE_HOPS,
   tastes excludes Taste.SPICY,
   tastes excludes Taste.COFFEE)
then
   $beer.setRecommendedBeer("Mendocino White Hawk IPA");
end

Note that the copasetic rules syntax isn’t too hard to pick up– it’s quite logical: if the BeerPreference’s color property is light and the collection of Tastes includes Taste.HOPPY and Taste.AGGRESSIVE_HOPS and also doesn’t contain Taste.SPICY and Taste.COFFEE, then the rule engine will take the BeerPreference instance (which is $beer) and set the recommended beer to "Mendocino White Hawk IPA" (which, by the way, is an excellent beer). Drool’s rules syntax is simple– object attributes are obtained via their proper name, rather than by a getter method, logical ands are denoted via commas and binding variables is done via the : operator.

Testing rules is most easily done via table based frameworks like Fit. Writing tests via JUnit or TestNG, while possible, can become laborsome due to the number of combinations one must code. Nevertheless, I can code a simple sunny-day scenario test case via JUnit to demonstrate Drool’s in action.

First, I must initialize Drools, which involves loading my rules (find in the file beer-guide.drl) and adding them to a Drool’s RuleBase like so:

public class BeerPreferenceTest {
 private static RuleBase ruleBase;

 @BeforeClass
 public static void setUpBeforeClass() throws Exception {
  Reader source =
   new InputStreamReader(
     BeerPreference.class.getResourceAsStream("beer-guide.drl"));

  PackageBuilder builder = new PackageBuilder();
  builder.addPackageFromDrl(source);
  final Package pkg = builder.getPackage();
  BeerPreferenceTest.ruleBase = RuleBaseFactory.newRuleBase();
  BeerPreferenceTest.ruleBase.addPackage(pkg);
 }
}

Now that Drool’s is read to go and because it’s my bag, I can create an instance of WorkingMemory and pass in my BeerPreference instance. Remember, you must call the fireAllRules method on your WorkingMemory instance to force things to happen.

@Test
public void verifyIPA() throws Exception{
 WorkingMemory workingMemory =
 BeerPreferenceTest.ruleBase.newWorkingMemory();

 BeerPreference beer = new BeerPreference();
 beer.setColor(Color.LIGHT);
 beer.addTastePreference(Taste.HOPPY);
 beer.addTastePreference(Taste.AGGRESSIVE_HOPS); 

 workingMemory.assertObject(beer);
 workingMemory.fireAllRules();

 assertEquals("Should be Mendocino White Hawk IPA",
   "Mendocino White Hawk IPA",
    beer.getRecommendedBeer());
}

Using a hip rules engine doesn’t necessarily reduce complexity– it just isolates portions of it into a format that can be manipulated by non programmers. In essence, a rules engine creates flexibility, while also providing for more testability. Note how in the test above, I was able to isolate my logic for IPAs without having to deal with any of the other six beers. With normal conditionals, I might have had to concern myself with the other choices, so as to force the IPA one. Luckily, my logic is quite simple so this testing challenge may not be entirely apparent.

If you find excessive logic that’s bag:

  • Changes often
  • Is the privy of domain experts who don’t write the code

then you may want to look into an expert system, which can centralize human-readable logic into one location. Rules engines aren’t a sliver bullet nor are they perfect for all scenarios; however, if applied correctly, they can decrease conditional complexity quite nicely.

13 Responses to “Curtail complexity with a rules engine”


  1. […] Original post by Andy and software by Elliott Back […]

  2. on 25 Feb 2007 at 11:00 pm Andy Stopford

    Hi,

    In terms of row based testing you could (if using .NET) unit test your rules using MbUnit and its row test.

    http://www.mertner.com/confluence/display/MbUnit/RowTestAttribute

    Cheers

    Andy Stopford

  3. on 25 Feb 2007 at 11:48 pm Andy

    Thanks, Andy for the pointer to MbUnit!

  4. on 26 Feb 2007 at 9:09 pm willCode4Beer

    Best example of a rules engine yet
    ;-)
    I recently posted about using these instead of convoluted logic but, I didn’t bother explaining what one really is. I’m linking back to this because you’ve explained it fantastically.

  5. on 26 Feb 2007 at 9:31 pm Andy

    willCode4Beer– I love your name, man. Your blog entry entitled
    Becoming a Better Developer” is excellent and is a must read. Well done– enjoy a cold one on me!

  6. on 26 Feb 2007 at 10:31 pm Rolando Hernandez

    This is a great example of hard-coding rules vs rules in a rule engine.

  7. on 26 Feb 2007 at 10:35 pm Andy

    Thank you, Rolando– keep up the great work with the BizRules Blog!

  8. on 26 Feb 2007 at 10:45 pm BizRules Blog

    Curtail complexity with a rules engine…

    Interesting article by Andrew Glover at The Disco Blog on how rule engines remove complexity from rules, and as a result simplify the task of programming rules. His example is beer recommendation rules, and that alone will make most of you cl…

  9. on 26 Feb 2007 at 10:52 pm Dave

    This word, copacetic, I do not think it means what you think it means.

  10. on 28 Feb 2007 at 10:21 am Rajgo

    Hi,

    Very nice example you have there. I wanted to add a few other points that I think are important.

    1. You can use a Rules Engine, to automate your business decisions

    2. Capturing Business Policies are Business Rules gives you instant visibility into how your system makes decisions. This will mean that a business manager or a business analysts can participate, and eventually control the formulation and evolution of the business rules.

  11. on 28 Feb 2007 at 3:28 pm Andy

    Thanks for the additional points, Rajgo! Your blog entry entitled “Business Rules & the Software Reuse Debate” is excellent too.

  12. on 02 Mar 2007 at 5:50 pm Tom

    This is the best introduction to rules engines I have seen. Thanks


  13. […] Den recht unterhaltsamen Artikel vor allem mit weiterführender technische Tiefe kann man hier Curtail complexity with a rules engine nachlesen. […]

Trackback this Post | Feed on comments to this Post

Leave a Reply