Thursday, March 15, 2007

Writing better rules (Edson Tirelli)

A frequent question we got is "how do I write better rules?" or symptomatic variations of that like:

  • What am I doing wrong? I have so much procedural code in my rules...
  • How do I call a specific rule? (that comes from 2 types of people: those used to backward chaining and those used to procedural code)
  • How do I iterate over a collection?
  • How do I execute an action in the LHS?

All the above questions may expose some level of misunderstanding of declarative programming and/or lack of knowledge in forward chaining engines.
DISCLAIMER: This is not negative criticism. This is only an acknowledgement that we, "rules people", need to do more on the evangelization side. We need to make it easier for people to understand and learn the principles of declarative programming.

Having said that, we all know that writing rules is a creative job and there is no single way of doing it, in the same manner there is no single way of interpreting a song (unless it is a Mozart's composition). There is no right or wrong, but there are better and worst ways (even being a bit subjective).

So, I will try to give some tips on how to identify the symptoms that may indicate that something is not going well with your rules, and how to improve the rules making those symptoms disappear.

The Symptoms

1. Too much procedural code:
If your rules are requiring several functions to be created or your consequences are getting complex, it may be a sign that something is wrong. Rules are supposed to be a declarative description of a pattern you are looking for in your facts and the consequence is supposed to be a sequential list of actions to execute when that pattern is found.

2. Flow control structures in the consequence:
If your rules contains flow control structures in the consequence, like "if" or "switch", there is a high probability your rules are doing something in a really bad way. Loop structures like "for" and "while" may be required in some very rare cases, but they are often enough a sign of problems in the rules too.

3. Extensive use of eval():
Mark my words: "EVAL IS EVIL". Eval is a very flexible CE, but it is also a BIG problem too. To start with, your rules frequently become unreadable if you use it extensively. Also, since you can do almost anything inside an eval, the engine can not make any assumptions about it and so it is really difficult to provide any optimization. In JBoss Rules 3.0.x, there are some situations that require the use of eval(), but we worked a lot on improving expressiveness of the language and, in JBoss Rules 3.1.0-M1 and later, we reduced a lot the situations that require eval(). So, if you are using a lot of evals, you probably can do better.

How to improve the rules

1. Business Domain Model:
Probably the most important single item you need to worry about is how expressive is your Business Domain Model. A simple analogy is to compare it with a data model and an RDMS system: we all know that the performance, simplicity and efficiency of SQL queries are directly related to the quality of the data model. The same way, the performance, simplicity and efficiency of the rules is directly related to the quality of the business model. Different Rule Engines have different ways of representing your Business Model, but fundamentally, your Business Model will be an instantiation of the ontology of your business. In other words, you know what your business is, what the requirements to running it are and what you need from your information systems. So, formally describe that ontology using your rules engine "language".
JBoss Rules/Drools works natively with Java Object Oriented Models, but it also allows you to define higher level models using Decision Tables, Domain Specific Languages, or any other language you provide a driver for (see Mark's post about supporting CLIPS, for instance).

So, tips for you to create good models for applying rules:

  • prefer flat models over deeply nested ones. It is always easier to write rules for flat models.

  • ask yourself the questions your rules will ask and see if you can answer them through the use of your Business Model. If it is confuse, refactor your model and try again. Simple example: your rules will apply over orders, products, and the products in the orders (let's call them items). So, does your model allow you to know about orders? Does your model allow you to easily know about the items that compound your order? Does your model allow you to easily know the details of the products in each of the items of your order? As you can see, this is a very similar exercise we go through when designing data models.

  • write your business rules in your natural language (English, Portuguese or whatever). Identify all objects referenced in your rules. For each object identify all referenced attributes. For each attribute, identify all constraints applied over that attribute. Now check if your business domain model contains those objects (facts) with those attributes and if it is possible to apply the constraints over those attribute types. It may appear like we are doing Object Oriented analysis here, but it is not really the same; we are talking in a higher conceptual level, since, whatever the tool you use, it will need the same information, being it OO based, template based, or even a higher level DSL.
2. Know your tool:
It is obvious, but good to reinforce. Know the tool you are using and how to express knowledge using its "interface", be it a plain textual language, spreadsheets, web interfaces or whatever. Learning a tool takes time and investment, but it is worth it. You will write better and simpler rules using advanced features. A simple example: I often see people using eval() and functions to write constraints in JBoss Rules that would be much simpler, efficient and performant if written as simple pattern constraints. (remember: EVAL IS EVIL!)

3. Assert all your objects as facts:
Rule engines usually know how to handle a fairly heavy load of facts, so let the engine do its magic. If you want to reason over a nested set of objects, assert all of them (not only the top level ones) into the engine and make sure your business model models the relationship between them. After that, writing your rules will be easier and the engine will be able to optimize execution (when compared to using eval and other constructs to access deeply nested structures). Example: if you have an Order fact that contains a list of OrderItems, assert both Order and OrdemItems as facts (if you need to reason over OrderItems too, obviously).

All the above tips have exceptions, but for the general case, following the above tips will help you write better rules.

Resources

There are good books reference in the documentation page and I recommend those interested in learning more to read them. Writing good rules is usually very simple for those "initiated", but may not be so simple for those "initiating"... :) I guess it is like using a spreadsheet: once you know your way, it is an incredible powerful and flexible tool, but you may need a hand to start using it.

I will try to follow up this post with concrete examples of writing rules. Maybe it will be helpful for some of you.

comments