Naked Panda Meditations

    zazeninjector

    1.0.0 • Public • Published

    ZenInjector

    Wercker status

    A simple library for dependency injection with support for ES6 generators.

    Dependency what ?

    With dependency injection, one doesn't care how to retrieve dependencies as long as they comply with a given interface. This allow you to decouple each components from each others. You don't need to know which implementation of express or fs you're using. This allow easy mocking of objects for testing for example. Using dependency injection, you create once a container which will manage all the components of your application, and make them available where they are needed.

    Use cases

    You should use zeninjector when

    • You want to decouple your components from each others
    • You want to integrate into one namespace a lot of components
    • You need to dynamically add components to your code without branching the whole repo.
    • You want to be able to do (more) unit tests on your code.

    Example

    Below, a typicall example of any node.js program:

    // file module1.js
    var dep1 = require('./dep1');
    var dep2 = require('./dep2');
    module.exports.myModuleName = dep1 + dep2;

    Two flavors

    You can rewrite this snippet of code with zeninjector using it programmatically or with annotations.

    Programmatic usage

    Create the IOC container:

    var Zeninjector = require('zeninjector');
    var container = new Zeninjector();

    Register a module with some dependencies:

    container.register('myModuleName', function(dep1, dep2) {
      return dep1+dep2;
    });
     
    // alternative syntax
    container.register('myModuleName', ['dep1', 'dep2', function(dep1, dep2) {
      // with this syntax, you can name the function's arguments whatever you like
      return dep1+dep2;
    }]);

    Directly register a module with no dependency:

    container.registerAndExport('myConfigObject', {
      env: 'test',
      port: 8080
    });
     
    // it works great with `require` too
    container.registerAndExport('Promise', require('bluebird'));

    Resolve a module with its dependencies:

    container.resolve('myModuleName').then(function(module) {
        // here module is equal to `dep1+dep2`
    })

    Zeninjector#resolve returns a bluebird promise, thus the need to have a then.

    Inject dependencies to an anonymous function, because sometimes you just want to access some objects once, but still want all the goodness of the IOC.

    container.inject(function(myConfigObject, dep1) {
      console.log('current env %s, dep1 equals: %s', myConfigObject.env, dep1);
    });
     
    // this also works
    container.inject(['myConfigObject', 'dep1', function(config, dep) {
      // ...
    }]);

    Annotations

    You still have to create the container:

    var Zeninjector = require('zeninjector');
    var container = new Zeninjector();

    Scan your projects to automatically register all dependencies:

    var scan = container.scan([__dirname+'/lib/**/*.js', __dirname+'/ext/**/*.js']);
    

    This returns a bluebird promise. Once it's done, you can use Zeninjector#inject and Zeninjector.resolve to get a reference to your defined objects.

    Define a module with dependencies:

    //@autoinject
    module.exports.myModuleName = function(dep1, dep2) {
      return dep1+dep2;
    };

    With custom name and custom dependencies:

    //@autoinject(name=myModuleName; dependencies=dep1,dep2)
    module.exports.thisNameIsIrrelevant = function(a, b) {
      return a+b;
    };

    This example has exactly the same effect as the previous one.

    Automatically exports (similar to Zeninjector#registerAndExport):

    //@autoexport
    module.exports.myConfigObject = {
      env: 'test'
    };

    Asynchronous definitions

    Let's have a look at an example where you need to connect to a database:

    // file db.js
    var MongoClient = require('mongodb').MongoClient;
    var db;
    module.exports.connect = function connect(callback) {
      if(db) {
        process.setImmediate(function() { callback(null, db); });
      } else {
          MongoClient.connect("mongodb://localhost:27017/exampleDb", function(err, _db) {
            db = _db;
            callback(err, db);
          });
      }
    };

    There is a problem here, everytime one wants to connect to the database, connect has to be called first and your code ends up in another callback (hello callback hell). With zeninjector, a module can returns a promise, and the result of this promise will be injected as dependecy:

    var MongoClient = require('mongodb').MongoClient;
     
    // @autoinject
    module.exports.db = function(Promise) {
      // inject a Promise library
      var connect = Promise.promisify(MongoClient.connect);
      return connect("mongodb://localhost:27017/exampleDb");
    }
    // @autoinject
    module.exports.myOtherModule = function(db) {
      // db here is the database object ready to be used
    }

    With generators

    resolve and inject returns a promise so it can easily be used in coroutines. Below is the asynchronous example written with generators:

    var Promise = require('bluebird');
    Promise.coroutine(function* () {
      yield container.scan('**/*.js'); // scan the db.js file
      
      try {
        var db = yield container.resolve('db');
        var foo = db.collection('foo');
        //...
      } catch(err) {
        console.error('Got error:', err);
      }
    })();

    This example requires node >=0.11.4 with the flag --harmony-generators.

    Running tests

    npm install && npm test
    

    API

    new Zeninjector(Object options) -> container

    The options object currently supports:

    • logger an optional logger (default to the console). The logger must implement the methodes trace, debug, info, warn, error and fatal.
    • verbose to add more logs with the level DEBUG. Defaults to false.

    .setLogger(Logger logger) -> container

    Set a custom logger after creation. The logger object is expected to have the following functions: trace, debug, info, warn, error and fatal;


    .register(String name, FunctionDefinition) -> undefined

    This function will register the dependency name. FunctionDefinition can be a function or an array of Strings, with its last element being a function. The array variant is the same as require.js. If only a function is provided, the name of the arguments will be used to fetch the dependencies, same is angularJS implicit dependencies.

    When this module is required, the given FunctionDefinition function will be called and it's return value will be used as the value of the module name. If it returns a promise, the resolved value of the promise will be taken.


    .registerAndExport(String name, Any value) -> value

    This is a shorthand to container.register(name, function() { return value; });


    .isRegistered(String moduleName) -> Boolean

    Return true if and only if there is a registered module with the given moduleName.


    .resolve(String name) -> promise

    This will activate the define function for the dependency with name. The returned promise will resolve to the return value of the define function.


    .inject(FunctionDefinition) -> Promise

    This will invoke the given function with its arguments already resolved.


    .scan(Array patterns) -> promise

    Scan takes an array of file glob patterns (or a single string) and returns a promise which resolves when all the files have been scanned. Scan will look for //@autoinject and //@autoexport inside every files, and take the following function's name as the module name. This method allow to manage large project without having to pass around the container object and do the registration by yourself.

    Annotations can be used to define a custom name for the module and specify dependencies explicitely:

    //@autoinject(name=customName, dependencies=dep1,dep2)
    // dependencies are comma (,) separated
    

    The following patterns are supported to declare your module with annotations:

    module.exports = {
      //@autoinject
      a: function() {}
    }
     
    //@autoinject
    module.exports.b = function() {
      return 'baz';
    };
     
    //@autoinject
    var c = function() {};
     
    //@autoinject
     
    function d() {};
     
    //@autoinject
    function e() {};
    // /!\ this will raise an error if you try to require it
    // .scan's promise will be rejected here.
     
     
    function f() {}; // will NOT be exported
     
    //@autoinject
    module.exports = function g() {};
     

    License

    MIT

    Install

    npm i zazeninjector

    DownloadsWeekly Downloads

    0

    Version

    1.0.0

    License

    MIT

    Last publish

    Collaborators

    • pjk