Hi Ant,
It's a great module that you have there.
A few years back I wrote a similar rules engine but it's proprietary work
which I can't share and which I'm not in possession anymore. I apologize as
I can't be too specific so I'll err on the cautious side.
Here are a few things I learned or opinion I have after building that
engine. Most of it is probably just rambling words but hopefully there is a
bit of value that you can leverage.
1) Keep in mind that the context that I was in was to provide a distributed
system. That means objects are serialized, at least between the client and
server. Also, a rules engine is a state machine. That state needs to be
temporarily stored somewhere, whether in DB, in the client or in some
server side cache (memcache, redis, ...).
2) We first used Nools (https://github.com/C2FO/nools). If you have looked
into javascript rules engine, I'm sure you encountered Nools. It is a JS
implementation of drools, a Java based rules engine. Nools implements some
kind of RETE algorithm. Your module has a lot of similarities with Nools.
Nools had 2 issues for us:
- terrible performance (we realized that half way into the project)
- pretty bad filter API (we realized that very early)
It was pretty clear that RETE was a good place to start as it is a well
known algorithm for rules engines.
With RETE a rule applies to one or several entities and can modify these
entities.
The goal of RETE is to minimize the CPU cycles necessary to execute all the
rules that should apply to a given set of entities. Hence that hierarchy:
type -> filters -> logic
I think that the biggest problem that we face with RETE and Javascript is
the fact that JS doesn't really have types. And when it does, raw JSON does
not include it in the serialized streams.
You don't have that issue as you're using namespaces instead of entity
types. But I would argue that you should not have to pass in a namespace.
The rule engine should figure out which rules to run based on what data you
are passing to it. A bit like a generic workflow that sits on to of you DB,
3) JS in, JS out
Something that bugged me with Nools and that bugs me with your
implementation (@ajlopez mentions it) is that for some reason, the filters
use some kind of domain specific language (DSL) instead of functional
Javascript. You would want to keep your rules engine as powerful as
possible.
I understand that you may want an administrator to edit the rules and you
wouldn't want that admin to write javascript. That's ok but then there
should be an additional layer specific to your app that does the
transformation from DSL to javascript but the engine should run javascript
and nothing else.
The reason I'm saying this is because I was bit by that. Being limited in
functionality because the rules don't support more advanced filters was a
moment when I realized I failed the architecture. It was quickly worked
around but something worth avoiding.
4) Rules definitions
With Nools, a rule definition has 3 parts:
- the types of entities it applies to
- filters to apply when the types match (conditions to know whether to
apply the rule or not)
- logic to apply if the filters match
That is a good thing because it gives 2 advantages:
- possible performance optimization by allowing to retrieve rules based on
the data type.
- semantic separation between filters and logic (arguably, the filters can
be implemented in the logic).
5) Session resumption
In a distributed system, it is very important to be able to serialize a
state, deserialize it and resume rules evaluations.
Nools did not allow it. It did not even allow to easily merge deserialized
data into an existing rete instance. It looks like it still can't do that.
I'm not sure about your implementation.
The alternate is sticky sessions.
6) Performance
We had performance issues. When I looked into it, I realized that the most
time lost was within Nools. When digging deeper, it was clear that Nools
was doing plenty of things that we did not need. There was quite some room
for improvement.
I ended up rewriting a rules engine specific to our needs but generic
enough that it was still an all purpose rules engine. For a complex rule
set, we went from 1500-2500ms execution time to 20-50ms. That's a 50x
performance improvement.
Your lib is great anyway and I'm sure many people will find it useful !
- Nicolas