0.1.3 • Published 9 years ago

nomade-kernel v0.1.3

Weekly downloads
3
License
MIT
Repository
github
Last release
9 years ago

Nomade kernel Build Status

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 value
  • container.has('repository'): tests a service existence, returns a boolean
  • container.get('repository'): returns a service by its ID, throws an Error if service does not exist
  • container.create('repository.query', { ... }): creates an object using a registered factory
  • container.emit('channel', { ... }): will emit an event on the given channel
  • container.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
  • 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
  • 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']; }
    }
};
0.1.3

9 years ago

0.1.2

9 years ago

0.1.1

9 years ago

0.1.0

9 years ago