Simple rule engine ported to Node.js

1,640 views
Skip to first unread message

Ant Kutschera

unread,
Nov 22, 2014, 3:14:51 AM11/22/14
to nod...@googlegroups.com
Hi,

I have ported my simple rule engine from Java to Javascript and uploaded it to the npm registry: https://www.npmjs.org/package/maxant-rules

The code is at https://github.com/maxant/rules/tree/master/rules-js and I would be interested in feedback since its my first node module.

Thanks,
Ant

Angel Java Lopez

unread,
Nov 22, 2014, 1:49:52 PM11/22/14
to nod...@googlegroups.com
Interesting... good job for a first module!

Some quick comments:

- Instead of "input.prop >= 45" you could add the support of passing a JS function:

function(input) { return input.prop >= 45 }

But maybe you are constrained for support for Java8 and Scala implementations (I guess they could have functional values to pass as arguments, too)

- Instead of eval("input.prop >= 45") you could internally convert it to a function (once):

rule.fn = new Function("input", "return input.prop >= 45")

And yes, this is the time to "me too" ;-)
No README, samples, but tests

No priorities in rules, I'm interested in fired all suitable rules over a given model. I'm using a fluent interface. Conditions and actions can be defined by specific objects, or by functions. To do: when the model changes (after an async operation, maybe triggered by an action or somethin else), rerun any pending rules. To do: support for rule backtrack?

Personally, I prefer to have a package.json with short dev dependency list, at least for the first iterations/versions. But I guess this is a personal taste/decision.

Angel "Java" Lopez
@ajlopez


--
Job board: http://jobs.nodejs.org/
New group rules: https://gist.github.com/othiym23/9886289#file-moderation-policy-md
Old group rules: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.
To post to this group, send email to nod...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/nodejs/dbd4a662-6674-4e25-bc65-d312d86cd47e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Alex Kocharin

unread,
Nov 22, 2014, 4:25:32 PM11/22/14
to nod...@googlegroups.com
 
I have a question that's not related to the code itself, but...
 
What LGPL even means when applied to javascript projects? I didn't see it used much around here, and there is no linking involved.
 
 
22.11.2014, 19:06, "Ant Kutschera" <ant.ku...@gmail.com>:
--

Ant Kutschera

unread,
Nov 24, 2014, 3:28:45 PM11/24/14
to nod...@googlegroups.com
Hi,


On Saturday, 22 November 2014 19:49:52 UTC+1, ajlopez wrote:
- Instead of eval("input.prop >= 45") you could internally convert it to a function (once):

rule.fn = new Function("input", "return input.prop >= 45")


Does that make it execute faster in the long run?

And would Node throw an error on that line where the function is constructed, or only when the function is executed?

Thanks,
Ant 

nicolas

unread,
Nov 25, 2014, 3:27:48 AM11/25/14
to nod...@googlegroups.com
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
Reply all
Reply to author
Forward
0 new messages