nomade-kernel
v0.1.3
Published
Nomade framework kernel
Downloads
6
Readme
Nomade kernel
The Nomade kernel provides a simple SOLID architecture using a service & configuration container and a flexible extension system. Your application is empty by default, functionalities only are provided by extensions.
Quick start
How to install?
npm install nomade-kernel
How to test?
cd node_modules/nomade-kernel
npm test
How to use?
// creates kernel and run its server service
require('nomade-kernel')
.create({ config_dir: __dirname + '/app/config' })
.register(require('nomade-core'))
.register(require('nomade-server'))
.register(require('./app/extension'))
.get('server')
.run();
Container and services
Nomade framework is entirely built around a simple principle: the service container. Each functionality is provided by a service accessible through the container.
Services container
The services container role is to provide services and configuration values (and help services to communicate) using a very simple API:
container.read('repository.default.database', 'nomade')
: reads a config value by its path, and provides an optional fallback valuecontainer.has('repository')
: tests a service existence, returns a booleancontainer.get('repository')
: returns a service by its ID, throws anError
if service does not existcontainer.create('repository.query', { ... })
: creates an object using a registered factorycontainer.emit('channel', { ... })
: will emit an event on the given channelcontainer.promise()
: instantiates a promise (for convenience, avoid requiring the class file)
Service definition
A service is a stateless object (the same instance is provided each time a service is accessed) which can own following attributes types:
- a method:
- always return a
Promise
instance - can be decorated
- always return a
- an object:
- is considered as service itself
- can be accessed directly through the container
- can be accessed by parent service through
this
- can access parent service through
this._owner
- can be extended by decorators
- an array:
- is considered as a registry
- can be accessed by parent service through
this
- can access parent service through
this._owner
- can be extended by decorators
Other attribute types are simply omitted in order to avoid design errors. A service is just a functions container and must be stateless.
Service method
A service method always returns a Promise
instance (read more about promise system below).
But there is a special case, when a sync function directly returns a value, the framework will return a promise for you.
var sample = {
// calling this method will return a promise
sayHello: function (name) { return 'hello ' + name; }
};
Service registry
A registry is a simple array of objects which can be used by services. It permits implementation of flexible designs.
var repository = { // the `repository` service definition
engines: { // `engines` child service
mysql: function (config) { /* returns mysql repository engine */ }
},
adapters: [ // `adapters` registry
{ supports: function(entity) { return true; }, engine: repository.engines.mysql }
],
update: function (entity, values) { // `update` method
for (var index in this.adapters)
if (this.adapters[index].supports(entity)
return this.adapters[index].engine.update(entity, value);
}
};
Decorating service
Each extension is able to decorate existing services.
Promises system
- A promise is a way to handle method results by unifying sync & async calls.
- A service method always returns promise.
Promise creation
To create a promise, you must do like this:
mysql.update = function (entity, data) {
var promise = container.promise();
this.engine.execute('update ...', function (error, result) {
if (error) promise.dispatch(Promise.ERROR, error);
else promise.dispatch(Promise.DONE, result);
);
return promise;
}
We can simplify this because:
- promise can create handler function using the right strategy and the default strategy, named
errorAsFirstArgument
is doing exactly the same thing as above; - the promise constructor can take a function which will be executed bound (
this
) to the promise itself.
mysql.update = function (entity, data) {
var engine = this.engine;
return container.promise().call(function () { engine.execute('update ...', this.handle()); );
}
Promise usage
Promises are simple to use, you just have to register function called when job is done or throws an error:
container
.get('repository')
.update(user, { firtName: 'John', lastName: 'Doe' })
.done(function (user) {
/* user is stored, you can continue to work with it */
})
.done(function (user) {
/* you can register many listeners for the same event */
})
.error(function (error) {
/* an error occurred, you should do something to handle it */
})
;
Promise interception
When it comes to decorate a function, catch result of the parent function and return a new result based on it is a real pain because of the promises. To handle this, you can use intercept
method like that:
var decorator = {
getName: function (parent) {
return parent
.getName()
.intercept(function (name) { return name + ' decorated'; });
}
};
Extension system
An extension is a function taking a Builder
instance as argument.
// exports your extension for node
module.exports = function (builder) {
builder
.configure(/* config loader */)
.substitute('extension.directory', __dirname)
.construct('my_service', /* service constructor */)
.factor('my_factory', /* object factory */)
.listen('channel', /* event listener */)
.decorate('decorated_service', /* service decorator */);
.validate(/* container validator */)
;
};
Config loading
A config loader is a function
- that takes current config (a
Config
instance) as argument - who must return an object which is deeply merged to current config
builder.load(function (config) {
return { param: 'value' };
});
Service construction
A service constructor is a function
- that takes current config (a
Config
instance) as argument - who must return an object
builder.construct('service_id', function (config) {
return { method: function () {} };
});
Object factoring
An object factory is a function
- that takes
- the service container as 1st argument
- the arguments passed to the factory as other arguments
- who must return a promise
builder.factor('user', function (container, username, password) {
return new User(username, password);
});
Event listening
A event listener is a function
- that takes an event as argument
- who doesn't return anything
- you can specify a priority as 3rd argument
builder.listen('error', function (event) {
event.container.get('logger').log('error', event.error.message);
}, 0);
Service decoration
A service decorator is a function
- that takes
- the decorated service (an object) as 1st argument
- the container (a
Container
instance) as 2nd argument
- who must return an object
- you can specify a priority as 3rd argument
builder.decorate('service_id', function (parent, container) {
return { method: function () {
// do something more
return parent.method();
}; }
}, 0);
Container validation
A container validator is a function
- that takes
- the container (a
Container
instance) as 1st argument - the violations (a
Violations
instance) as 2nd argument
- the container (a
- who doesn't return anything
builder.validate(function (container, violations) {
if (!container.has('server')) {
violations.add('The `server` service is missing, its required by the `api` extension');
}
});
Factory system
A factory repository for patterns and processes that are common in Nomade's land.
var factory = require('nomade-factory').get('package.module');
var result = factory(/* arguments */);
// or using the create function
var result = require('nomade-factory').create('package.module', /* arguments */);
Service adapater
Used to define services using adapter pattern.
var definition = require('nomade-factory').create(
'service.adapter', // the factory name
container, // a child container for this service
{/* adapters */}, // service adapters definitions, indexed by ID
{/* methods */} // service methods definitions, indexed by name
);
Adapters definitions should look like this:
var adapters = {
'adapter-id': {
// construct method is an optional constructor returning an engine
construct: function () { return engine; },
// each service methods must be defined, this represent the engine (if defined)
method1: function (parameter) { return this.doSomething(parameter); }
}
};
Methods definitions should look like this:
var methods = {
// all service methods are defined here
'method-name': {
// the minimal definition is the name of the method
method1: {},
// you can define a resolve method to intercept and resolve parameters
method2: { resolve: function (param1, param2) { return [param1, param2 || 'default']; }
}
};