Rule Engines, Needed?
Why Rule Engine?
As it is unfolding now, despite many tries from the other sides of the force, most of the languages used by industry are imperative in nature. It is also a matter of fact that sooner or later, developers using such languages and paradigm figures out that it is a sin to write business logic inside compiled code, or rather any imperative code. For expositions on how this realisation happens please read :
Hence, once the maturity and the complexity levels reaches a certain threshold, an organisation try switching to the closest declarative style of storing business logic a.k.a business rule engines. They are indeed the poor mans (imperative languages) declarative implementation (more like a proxy). One can put rules declaratively, and it's appropriate execution would be taken care by the engine.
What Types Are There?
There are two general classes of rules engines.
- Type -1 : the direct result of A.I., and generally uses inferencing using some modified versions of rete algorithm. It may also have a hidden model under the hood, for probabilistic sophistication.
- Type - 2 : event driven, and acronym as ECA : Event-Condition-Action.
You can read more about them in some detail here.
Now, I am not going to bore you with the first one, because no matter how fancy it sounds like, it never is good enough for serious inferencing. In fact whether or not one should use a rule engine is highly debatable topic. Instead, I am going to showcase how one can be easily build, based on a rule engine that everyone used, without knowing much about rule engines or Type-2 systems.
Meet Database Triggers
What Martin Fowler is not telling is that type 2 rule engines are used everywhere when one uses a database supporting constraints! Anytime you are using a database trigger, you are using a type 2 rule engine. Recall from databases that there is this thing called a trigger, which is used to implement this other thing called the constraint. Implementation of those are, simply put, nothing but type 2 rules. To showcase , suppose here is a db table schema with some constraint of non null and unique :
What is the mechanism for implementing this in the database level?
Event :
- Whenever any data modification is being done in that table,
- Observe that if the modification is being done to the *sno*.
- If it is, then fire the trigger.
Condition:
- The value that was to be set to the column *sno* is :
- either null or
- a value that already exists in some other row , then take action
Action :
- raise error saying : *shooh shooh shooh, constraint breaking not allowed*
In this form, it is easy to understand that what precisely is E-C-A.
Implementation Idea
Implementation of any Type-2 system, with the previous example, now becomes rudimentary. In fact it is so easy that like childhood science books - it should said that : "Left to the reader as exercise". Alas, gone those childhood days. So here is how with declarative tone one would implement it.
Events :
- Get a list of events where you want to put hooks. Example, for the database case, mostly putting hook before modification of any table data suffices.
- Now, generate an event filtering. In English, that would mean, given any change in database table would now be *monitored*, you need to specify under what condition this becomes your problem. In the previous case, that would be : Matching the tables name, or rather, in a very precise manner, full table name. Hence, the event filtering condition becomes :
t.name == 's'
Condition :
But hold on. The problem did not get over, yet. Now when this condition is matched, one needs to check that if any constraints over this table gets violated or not due to the change of data. So, suppose, the new row comes up as ( a JSON style object representation we did here) :
row = { "sno" : "42" , "name" : "Noga" , "city" : "Hooghly" }
Then, the before said constraint would be simply :
empty ( row.sno ) or row.sno @ s_table_ids
Failing this, it would now act:
Action:
throw constraint broken error.
error('Constraint breaking due to insertion of row ' + str( row ) )
and we are good. But wait, we are weaving hands too much into the clouds,
so we should actually make the code look compact and there should be some real executable code.
(unlike our friend Architects, we prefer actual working code )
Working Code : Draft
We would implement this system using 0.1 developer (me, because I am a QA) in the next 5 minutes. Observe that in the database level, there must be a table full of constraint which needs to be checked per table. Let's call this the constraint table. So, per table, there would be one such constraint table. (not true, but this abstraction works out, a db would be smart enough to query like select * from constraint_table where name='my_tab' ) Given a query, the db must find out what tables are effected - that is easy, find what tables are being written into. So, we need this tables will be modified event - which must give us the following :
- The tables
- List of parameters
Now, we should be able to filter individual tables with it's related parameters. The essential magic here is beyond scope for us as of now. Once we isolated a table as well as the parameters passed to modify it, we simply hand over the control to the table's constraints list, and loop over and use each constraint to check if it is broken or not. If nothing is broken, we can apply this change, for that table. We just need to do the same for all the tables affected by the query.
That brings up to :
so, what sort of code would one would put into the *constraint* for is_violated to work out? Here it is (see the section about currying here ) :
How the constraint body would look like? If you prefer to code in negative then :
And we are done.
With this much, lets still reduce the lines of code, because, well, Microsoft standard that I remember str is : 1 bug per 11 lines of code for good developers, and remembering that I am not even a developer, I should reduce it to much less:
And there is, that! Our experimental type-2 rule engine is up and running. Less than 10 lines too, phew! I am saved (?)! To understand some of these weird constructs like *lfold* or *error* or *bool* , you must visit Utility Functions.
NOTE:
This was written after having multiple discussions with Anubhav and Arup, and in some sense, they are the main motivators behind for this post, albeit they did not ask me to write it at all. Anubhab wanted a rule engine to be created and Arup wanted to find *the best rule engine* ever. In some sense, those discussions were the right thing to do. One needs a rule engine or not, or rather would try to smartly build one using right technology is their own discretion. Both ideas work, and work very well.
However to create a proper DSL, as Fowler was saying, scala might be your friend, and that is only if you want to make her your friend. Or rather if you want to create a Java based rule engine and looking for an appropriate scripting language for encoding the rules, look no further than nJexl. It is designed for such purposes.