Business Rules Engines and Declarative Programming
Reports, reports everywhere! Organizations have demand for flexible reporting capabilities more than ever. Standard data warehousing techniques solve data aggregation and summary type of reports. But there is another type of reports that can’t be generated this way. Consider a report that consists of N input KPI variables. Based on these N (50 for example) input variables, the algorithm shall produce M output variables (500 for example). The variables shall be calculated gradually using intermediate results.
The input consists of N = 4 variables; the output consists of M = 8 variables. Someone can create the following procedural logic for sure:
E = A + B F = C + D G = E + F J = G + E
But everyone that creates reports knows that calculation rules change. Very often! When variable inter-dependencies change, the sequential algorithm must change its shape and sequence of operations. Not very convenient!
Business Rules Engines and declarative programming solve the problem in a very elegant way. The calculation rules writer shall not consider the sequence of the rules defined. The inter-dependencies will be resolved automatically (there are cases where salience and rule execution order must be controlled, but in most cases that is not needed).
Drools is the facto standard as open source library in the Java world:
Drools Expert has efficient Rete network implementation that allows fast rule evaluation and execution logic (Alpha and Beta memory nodes, but that is a different story).
There is a similar Business Rules Engine in the Node.js world. Guess the name :-)? This Node.js library is called Nools:
The implementation of this library is also Rete Network based.
So, equipped with Nools, we can solve the above problem as:
calculations.nools
define Variable {
name: '',
value: '',
constructor : function(name, value){
this.name = name;
this.value = value;
}
}
rule 'Calculate J' {
when {
a : Variable a.name == 'A';
g : Variable g.name == 'G';
}
then {
var j = new Variable('J', g.value +'#'+ a.value);
assert(j);
}
}
rule 'Calculate E' {
when {
a : Variable a.name == 'A';
b : Variable b.name == 'B';
}
then {
var e = new Variable('E', a.value + '#' + b.value);
assert(e);
}
}
rule 'Calculate F' {
when {
c : Variable c.name == 'C';
d : Variable d.name == 'D';
}
then {
var f = new Variable('F', c.value + '#' + d.value);
assert(f);
}
}
rule 'Calculate G' {
when {
e : Variable e.name == 'E';
f : Variable f.name == 'F';
}
then {
var g = new Variable('G', e.value + '#' + f.value);
assert(g);
}
}
The JavaScript that invokes the rules above is:
calculations.js
'use strict';
const nools = require('nools');
const flow = nools.compile("./calculations.nools");
const Variable = flow.getDefined("Variable");
const session = flow.getSession();
const A = new Variable('A', 'A');
const B = new Variable('B', 'B');
const C = new Variable('C', 'C');
const D = new Variable('D', 'D');
session.assert(A);
session.assert(B);
session.assert(C);
session.assert(D);
session.match().then(
function() {
session.getFacts().forEach((fact) => {
console.log("%s: %s", fact.name, fact.value);
});
session.dispose();
},
function(err) {
session.dispose();
console.error(err.stack);
});
If we execute the script above, the output result is:
node calculations.js
A: A B: B C: C D: D F: C#D E: A#B G: A#B#C#D J: A#B#C#D#A
Fast and efficient!