I have the requirement to use a rule engine to implement role permissions in the system ( could this be an overkill? ) however the permissions are kind of complicated and complex itself. I got confused in how to grant access or not using a rule-engine.
I also have doubts about the design that I should use in order to implement it in a scalable and maintainable way. So any help in the design or explain to me how to use the rule engine would be great.
Using nools, mongoDB, node.js as backend.
I was thinking in creating a rule engine instance encapsulating Nools ( would be an anti-pattern inner-platform maybe?) in the bootstrap of my node.js app and let it as a global variable.
something like:
'use strict';
var nools = require('nools');
var flows = require('./rule-engine.flows');
// A flow is a container of rules, so each flow could be a category of rules
// In the same flow could have some more specific subcategories would be actionGroups?
// I'm creating a ruleEngine instance that would contain nools but I'm not sure
// if that would be a good practice, I have that to maybe encapsulate boilerplate code
// or some straight forward operations in the future.. don't sure of this.
var ruleEngine = function(){
this.nools = nools;
};
ruleEngine.prototype.getFlowSession = function(flow){
if(this.nools.hasFlow(flow)){
return this.nools.getFlow(flow).getSession();
}else{
var fl = this.nools.flow(flow, flows[flow]);
return fl.getSession();
}
};
ruleEngine.prototype.createRule = function(flow, rule){
if(this.nools.hasFlow(flow)){
// implementation to add rules to a flow
}
};
ruleEngine.prototype.editRule = function(flow, rule, update){};
ruleEngine.prototype.retractRule = function(flow, rule){};
//could be global object, or cache object but certainly should be a single object.
if(!GLOBAL.ruleEngine){
var ruleEngineInstance = new ruleEngine();
GLOBAL.ruleEngine = ruleEngineInstance;
}
//module.exports = GLOBAL.ruleEngine;
rule-engine.flow:
'use strict';
var flowName = function(flow){
// query the rules to database or to cache.. then add the rules to the flow.
// query bla bla function(results){
for(Var i=0; i<results.length; i++)
flow.rule(results[i].name, results[i].constraints, results[i].action);
// alternately, I could just from the bootstrap create a flow,
// and create a function to add, modify or retract rules of a specific flow.
// What would be the better design approach ? or combine two approach ?
// bring from database the first time, and later use ruleModify,
// ruleCreate or rule retract functions.
};
module.exports = {
flowName: flowName,
// each would be a flow that would be a category of rules for the system
flowName2: flowName2
};
How to use it to implement permissions, is the only way to communicate the rule engine and external app / code through events?
these are some rules I created just to mess about ( at the same time are the ones used to create the flowName simulating the cache rules or MongoDB rules ).
var results = [
{
name: 'userAllow',
constraints: [Object, 'obj', 'obj.systemRole === \'user\''],
action: function(facts, session, next){
session.emit('event:userAllow', {data: 'user is allow'});
next();
}
},
{
name: 'userNotAllow',
constraints: [Object, 'obj', 'obj.systemRole !== \'user\''],
action: function(facts, session, next){
session.emit('event:userNotAllow', {data: 'user is not allow'});
next();
}
},
{
name: 'adminAllow',
constraints: [Object, 'obj', 'obj.systemRole === \'admin\''],
action: function(facts, session, next){
session.emit('event:adminAllow', {data: 'admin is allow!'});
next();
}
},
{
name: 'adminNotAllow',
constraints: [Object, 'obj', 'obj.systemRole !== \'admin\''],
action: function(facts, session, next){
session.emit('event:adminNotAllow', {data: 'admin not allow'});
next();
}
}
];
So with this few rules, I just want to grant access when user.systemRole is admin for example.. should I use events in the following way?
X-method in the system:
//validate delete with ruleEngine... supposed only admin would be able to delete
var self = this;
var session = ruleEngine.getFlowSession('flowName');
session.assert({systemRole: User.role}); //User.role = 'user' || 'admin'
session.on('event:adminAllow', function(d){
console.log('do the job because the user is admin');
// Delete implementation.
});
session.on('event:adminNotAllow', function(d){
console.log('User not allow because is not admin');
});
session.on('fire',function(name){
console.log(name);
});
session.match().then(function(){
session.dispose();
});
So far I have some problems with this implementation.. events can fire more than once and I can't allow it to fire twice on a delete operation or a create operation or things like that.
So besides that error that I need to fix ( don't sure how ) Edit:
I commented the last next() of my rules, and after that the events are fired once. I have other doubts:
- Have good practices broken or anti-patterns?
- this is scalable and easy to maintain?
- Is the normal way of working with rule-engines?
- Pros and Cons of this implementation?
- Is there a better way?
Thanks in advance for any help.