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

7 comments:

  1. While I accept the thrust of your article, the three biggest obstacles that I see currently to widespread adoption of the JBoss rules syntax in business are: 1) lack of basic boolean operations in conditions and 2) the lack of nested property access and 3) the lack of proper null handling. These make it very difficult to write realistic business rules on anything close to a plain Java object model.

    I an glad to see that Michael and Mark are working on these issues (#1 and #2 have been partially addressed) and I think we should all encourage this line of refinement, even if it does not live up to the purist usage of the engine ;)

    thanks,
    Pat Niemeyer

    ReplyDelete
  2. Yes, these are some of the "expressiveness limitations in the language" I was talking about in version 3.0.x. We are working on removing these limitations (and many of them were already removed) in 3.1 and beyond, so please keep tuned and providing us with feedback. It is very important for us to know real use cases and user needs.

    ReplyDelete
  3. I would consider #2 lack of nested property access a rule Anti-pattern. In practice, you can't always change the object model, so some times it is nice to be able to access a nested attribute like customer.address.zipCode. The problem is that it creates more problems than it solves. Normally, when an object is asserted, engines like clips, jess and many others will cache the value of the attribute. If the rule language allowed users to access a nested field, should it make a deep copy of the object when it caches the values? Doing that is costly, so most engines do a shallow copy.

    From a rule patterns and practice perspective, it's generally better to explicitly assert the fact and match on the object.attribute. when that isn't practical, it may be better to create a Busines object model like JRules BOM that flattens the model and makes it easier for reasoning.

    ReplyDelete
  4. There are many outsource writing services providers are there online.. But when you select a provider for your custom business writing you must be very careful. A good business writing can improve and grow your business.. Gramatical or punctuation errors in your custom business writing causes bad impression on you//

    ReplyDelete
  5. Hi, I did the following and it seemed useful at the time -

    (a) Disregarded concepts like Inputs and Outputs to the Rule Engine - instead propagate the concept of a KnowledgeBase that undergoes some changes (becomes richer) as the rules fire. Spent almost as much time on thinking about the KB as about the rules.

    (b) Visualized the KnowledgeBase as a set of distinct XMLs, with tags and values in plain English, not more than two nestings per XML, maybe more than one XML.

    (c) Considered whether the KB to rules interaction would work even if the rules fired in a non-sequential unordered manner.

    (d) Wrote the rules first in plain English. As atomic as possible.

    (e) Asserted individual classes (once I JAXBed all those XMLs into classes) separately into Working Memory, but not the collections, which remained (hopefully as flat objects) inside each definite class.

    (f) Working with strings and other immutable classes always means "retract and re-insert" rather than update.

    (g) Minimized use of salience, but used activation-groups and no-actives whenever logic looked bunched or layered.

    I hope this may be of use to someone at the point of designing the rules set-up.

    ReplyDelete
  6. Is all your tips valid for drools 5 too?please update this valuable post.

    thanks,
    David Bakkar

    ReplyDelete