decision-engine
Install
npm install decision-engine --save
Description
A decision engine which based on fact and rules provided can deduce what decisions should be made. For example in an Ecommerce system, when users place orders there are multiple decisions to be made, whether the order qualifies for discount, what kind of shipping is applicable and so on. This calls for a business rule engine which decision-engine exactly is.
Rule
A rule is basically a JSON
with 4 properties
name
- name of decision, which is returned by the engine.group
- group identifier to which the rule belongs, a group can have multiple rules.comment
- description about rule just for readable purposesconditions
- actual rules are represented here
Where to keep rules ?
Rules can be stored externally in a .json file or in a document db like MongoDB. Storage can be in any format until engine is provided a json format.
Conditions
Conditions is a json object which can have either of and
or or
key, these corresponds to boolean &&
and ||
respectively. This and/or
key then could be a json array or json object depending upon the rule complexity.
The unit
of the condition is a json object which has three properties :
key
- key in the fact which is to be consideredvalue
- value to be compared with can be anything string, int, boolean.comparator
- this is the comparing function to be used.
This unit looks like this :
{
"key": "order.items.length",
"value": 2,
"comparator": "gte"
}
Now a condition can have multiple units paired under and
or or
key as elements of json array. This is shown below:
"and": [
{
"key": "order.items.length",
"value": 5,
"comparator": "lte"
},
{
"key": "order.price",
"value": 5000,
"comparator": "gte"
}
]
Let’s call this as a composite unit
. The boolean evaluation of individual unit will be reduced to single boolean value by applying either and
or or
function as defined by the parent key which is and
in above shown example. Now this composite unit can be directly referenced under conditions
attribute or can be nested under and/or
keys as json objects. Lets see an example.
Composite unit directly referenced by conditions
"conditions": {
"and": [{
"key": "order.items.length",
"value": 5,
"comparator": "lte"
}, {
"key": "order.price",
"value": 5000,
"comparator": "gte"
}]
}
Composite unit as an nested json object
"conditions": {
"and": {
"5ItemsPrice5000": {
"and": [
{
"key": "order.items.length",
"value": 5,
"comparator": "lte"
},
{
"key": "order.price",
"value": 5000,
"comparator": "gte"
}
]
}
}
}
Note that 5ItemsPrice5000
is a custom name and does not affect the rule syntax. It could be any string. Perhaps it would be more clear if you will see how two composite units can be combined.
"conditions": {
"or": {
"5ItemsPrice5000": {
"and": [
{
"key": "order.items.length",
"value": 5,
"comparator": "gte"
},
{
"key": "order.price",
"value": 5000,
"comparator": "gte"
}
]
},
"priceGreaterThan20000": {
"and": [
{
"key": "order.price",
"value": 20000,
"comparator": "gte"
},
{
"key": "order.items.length",
"value": 1,
"comparator": "is"
}
]
}
}
}
In the above example there are two composite units 5ItemsPrice5000
and priceGreaterThan20000
combined by an or
. This means either 5ItemsPrice5000
or priceGreaterThan20000
has to evaluate to true for decision to be applicable. This nesting of composite conditions with and
and or
can be done to a very deep level for supporting complex rules. Now its time to reveal how a rule
looks like.
{
"name": "10",
"group": "discount",
"comment": "10% discount applicable to order of more than 10 items with minimum total price of 500 or total order amount of 2000",
"conditions": {
"or": {
"5ItemsPrice5000": {
"and": [
{
"key": "order.items.length",
"value": 5,
"comparator": "gte"
},
{
"key": "order.price",
"value": 5000,
"comparator": "gte"
}
]
},
"priceGreaterThan20000": {
"and": [
{
"key": "order.price",
"value": 20000,
"comparator": "gte"
},
{
"key": "order.items.length",
"value": 1,
"comparator": "is"
}
]
}
}
}
}
Comparators available
is (a, that)
returns true if a === that
gte (a, that)
returns true if a >= that
gt (a, that)
returns true if a > that
lte (a, that)
returns true if a <= that
lt (a, that)
returns true if a < that
not (a, that)
returns true if a!===that
divisible (a, that)
returns true if a % that === 0
regex (a, that)
returns true if new RegExp(that).test(a) === true
API Usage
const decisionEngine = require('decision-engine')
Methods
addRule (rule)
rule is the json object having single rule. This will register the rule with decision engine.
decisionEngine.addRule(require('./orderSystem/discount.json'))
importRules (rules)
rules is the json array having multiple rule. This will register the rule with decision engine.
decisionEngine.importRules(require('./orderSystemRules.json'))
deleteRule (name, group)
This will delete the rule identified by name and belonging to group from decision engine.
decisionEngine.deleteRule('10', 'discount')
run (fact, group, callback)
Runs the rules from a group on the fact
and generates an array of applicable decisions’ names. callback is of standard format i.e. two parameters first being err
and second being result
. result
will be the array of applicable decisions’ names.
let fact = {
"user": {
"name" :"sikorski",
"address" : {
"state":{
"name":"Earth"
},
"express":{
"duration":1,
"available":true
}
},
"subs":{
"prime":true
}
},
"order": {
"items": [],
"price": 0,
"express": false,
"cashOnDelivery": true
}
}
fact.order.items.push({name: 'coffee mug', id: 'skuid1'})
fact.order.items.push({name: 'nodejs book', id: 'skuid2'})
fact.order.items.push({name: 'java book', id: 'skuid3'})
fact.order.price = 600
decisionEngine.addRule(require('./orderSystem/discount.json'))
decisionEngine.run(fact, 'discount', function (err, result) {
if (err) done(err)
else {
//result wil be ['10']
done(null, result)
}
})
Examples & Issues
Please refer test for example usage and feel free to log issues if any.