Beginners Introduction to Clara Rules

Charlotte Fereday
6 min readDec 23, 2018

--

This is the blog post version of a talk I gave at ClojureX 2018. If you’ve got 15 mins to spare you can see the lightening talk here, otherwise do read on!

This blog is going to cover 4 main points:

  1. Intro to Clara and rules engines

2. When to use them

3. When not to use them

4. Getting the best out of the paradigm

The source code used for this post is open source on Github here. I’ve added some further reading on Clara rules in the Readme, and if you’d like to learn more please add more rules and open up a pull request! I added a bunch of tests to my check assumptions and encourage understanding. I found very few examples of tests for rules engines, so hope this proves useful.

1. Introduction to Clara and rules engines

Easy set up

Clara is just another dependency that can be added to your project.clj file.

Example of how Clara rules can be added to your project

What are rules?

Let’s explain how Clara rules work through an example. You are a flatmate in a house which rewards its flatmates who complete their chores with beer. There are three key house rules:

“Only if you’ve done your four chores are you allowed to have a beer”🍺

“Our cleaners do the bathroom, living room and the kitchen” 😜

“If you’re ill you’re not getting any beer!” 🤕

We have here a series of statements of truths. Clara rules also works with statements of truth — which are its rules. These are are nothing more than a simple if-then conditional construct. For example:

If you’ve completed 4 chores ⇒ you’re entitled beer

If you hire cleaners to do chores ⇒ they will do 3 chores: bathroom, living room, kitchen

If you’re ill ⇒ you are exempted from having beer

A single rule

A Clara rule works in a similar way. On the left hand side facts are compared against conditions. If a condition is met, then it will go on to check the next condition. If all conditions are satisfied then the rule is activated and fired by the engine. Firing a rule will execute the action on the right, usually inserting or revoking a fact.

A Clara rule in action

It’s important to note here that there is only the If/Then condition, there is no else: if a condition isn’t satisfied then the rule will not be activated.

Multiple rules

Inside an engine rules are evaluated against a dataset called the working memory. The engine only knows about what is in the working memory, it won’d do DB queries, read files, or call an API. If data is needed to evaluate rules, it needs to have been inserted from the beginning.

Only rules can manipulate the working memory. There are therefore no side effects to invoking a rule. One the main benefits that this brings is truth maintenance. If the conditions of inserting a rule become false, then inserted fact is no longer supported, and Clara automatically retracts it from the session.

Rules evaluated against the working memory

In this way rules can interact with other rules, to express more complex conditional logic. The job of a rules engine is to evaluate a set of rules against some initial state of working memory

Basic algorithm for Clara rules

In a nutshell the basic algorithm for the rules is:

  • Find rules with all left hand conditions satisfied, i.e. activated
  • Fire one of these activated rules
  • Then, if rules are still activated, repeat from step 1

The rules engine stops when nothing can be activated.

2. When to use them

Managing complexity

Clara rules might become useful when “managing complex, potentially volatile, ‘business logic’ ” — Mike Rodriguez. This often translates to intertwined, repeated statements, causing a lot of time to be spent tinkering with conditionals and adding special guards in other places.

Other benefits also include being able to separate rules from queries, and by doing so enable a separation of concerns.

Conditional example

Here’s an example of our flat chores as a conditional statement. In this extract there’s a check to see if four chores have been completed by a flatmate, if they have then they are eligible for beer.

In one of the other conditions a check is made to see if a cleaner was hired, if they have been hired then the flatmate is also eligible for beer.

The problem that’s introduced here is that you need to concern yourself with order. If we changed the order and had this second condition before the previous one, then our logic would be incorrect.

Looking at the conditional statement as a whole, if we were to add in any additional rules it’s clear that it starts to become very unwieldy and quickly gains complexity. It’s also not ideal to keep adding keys and values to a data structure and be passing it around. It’s not very readable, as it’s hard to tell the rules from purely looking at the code.

Clara rules engine example

Now let’s look at how the flatmate chores would look if implemented using Clara rules.

On the left hand side (before the arrow), we create a series of conditions. First we bind the given Flatmate name to the ?name variable, then the number of chores completed by that flatmate are counted from the Chore fact and it’s number bound to the ?c variable, whose value needs to be greater than 3. If it is greater than 3, then the name and count are inserted into the CompletedChore fact, which can later be queried.

Looking at the cleaners rule, a clear benefit of using Clara rules is their readability. This reads just like the rule it’s representing.

Unlike the conditional statement, many more rules could be added and the conditional complexity increased whilst maintaining readable statements of truth. A clearer domain language has been created as the facts show the data points we care about, e.g. Chores, Exemptions, Completed chores.

Another benefit is that order doesn’t matter matter here, and truth maintenance is accounted for.

There’s no need to pass around a big blob of a data structure to be updated in one function, or multiple functions. Instead richer strategies for handling complex logic: e.g. booleans, inferred truths can be used to easily grow the complexity of the engine without affecting the others around them.

Facts — like whether or not a flatmate is eligible for beer — can be queried separately, and maintain a separation between logic of business rules and querying the results of those.

You can query any other fact without modifying or recomputing anything.

3. When not to use them

A rules engine won’t be a good choice when there’s simple business logic which is unlikely to grow in complexity and whose conditions can be adequately handled by if/else statements.

4. Getting the best out of the paradigm

Here are a few anti-patterns to avoid when using Clara rules..

Don’t program them like functions

  • Aim for many small records, rules & queries, as opposed to lengthy functions

Don’t write them like if/else statements

  • The benefit of paradigm is a separation of concerns
  • If they look like if/else- is your problem complex enough to warrant using rules?

Leverage algebra!

  • Be strategic in how you create conditions, for example you can check for the lack of a fact that matches a criteria for a rule (:not Exemption ?name).

--

--

Charlotte Fereday

Full stack dev @tes_engineering Security team. Proud alumni of @thoughtworks @CodeFirstGirls @makersacademy alumni 👨‍🎓