Require Bean

Minimal IoC container, designed for simplicity.

It's main concept is the container, in which the beans are registered. A bean is just a function that returns a value, like the following:

var mybean = function(){
    return {
        value: 'something'
        //anything here
    }
}

Installation

npm install require-bean

Quickstart

Follow the commented example:

/*
    Instantiate the container
    This line creates a container named 'myapp', to hold beans related to 'myapp' application
*/
var app = require('require-bean').container('myapp');

/*Define a bean that takes no dependency*/
var logger = function(){
    return {
        info: function(x){
            console.log(x)
        }
    }
}

/*
    Define a bean that depends on logger
    The dependency is passed as an argument to the bean function
*/
var greeter = function(logger){
    return function(){
        logger.info("Hello World")
    }
}

/*Register the beans*/
app.register('logger', logger);
app.register('greeter', greeter);

/*Require a wired bean from the container*/
app.run(function(greeter){
    greeter(); //This instance will run with the 'logger' dependency properly wired
});

There is also a helper method to register other modules into the container:

app.register_module('fs');

This will register the module with the same name into the container as a dependency for other beans. If the module's name contains hyphens or dots, it will be converted to a camelCase name:

app.register_module('token-manager'); //this will register a bean named tokenManager to hold the module.

Bean Notation

Alternatively, and preferably, you can use bean notation to register your beans. For that you should use the method app.bean() like the following:

var beanDef = {
   name: 'myBean',
   dependencies: ['aDependency', 'otherDependency'],
   scope: app.SINGLETON,
   factory: function(a, b){
      return "my awesome beans using " + a + " and " + b;
   }
}
//This registers a bean named 'myBean', with two named dependencies ('aDependency' and 'otherDependency') and singleton scope
app.bean(beanDef);

If you pass a dependencies array to the definition, the names of the factory function arguments will be ignored, and the names in the array will be used, in the proper order. If you omit the dependencies array or pass app.RESOLVE, the names of the arguments of factory function will be used.

This strict way to define a bean will accept an object with four fields:

  • name

    Required. This will be the name to be saved in the registry.

  • dependencies

    Optional. An array containing the names of the dependencies to be passed to factory. If instead you pass the constant app.RESOLVE, the container will infer from the names of the factory arguments
    Default: app.RESOLVE

  • scope

    Optional. You can pass container.SINGLETON or app.PROTOTYPE, so that your bean will be placed correctly in the registry.
    Default: app.SINGLETON

  • factory

    Required. The function that will return your bean. It will take the dependencies as arguments, and they will be resolved based on the dependencies parameter defined earlier.

If you write all beans in bean notation, there is a shortcut to register them all, so you can register all beans inside a directory at once:

var app = require('require-bean').container('myapp');

app.bean({ dir: __dirname +'/lib'}); //registers all beans inside lib directory

Asynchronous Beans

If your bean creation should be asynchronous, like registering a running http server, you need to wire the special dependency $return, provided out of the box:

    app.register('server', function(http, $return){ //
        var server = http.createServer();
        server.listen(8000, function(){
            $return(server);  // registers the server asynchronously;
        });
    });

Dependency Management

Since the beans are functions that return values, the dependencies are managed as these functions' arguments. require-bean will look in the named parameters for dependencies, so if you have a bean named awesomebean and need it as a dependency, you could write this:

app.register('mybean', function(awesomebean){ /* cool stuff here */ })

In order to do the correct wiring, the argument name must match the bean name. If no bean with that name have been registered, an exception will be thrown.

There is no restriction for the name of the beans you want to register, but they will be useless if you cannot define a function argument with the same name.

If the bean was defined in Bean Notation, the dependencies will be resolved differently, by first looking at the dependencies array provided, and using the function arguments as a fallback.

Bean Scope

When you register a bean, you can choose one of these methods:

  • app.register( bean_name, bean_function )

    This method registers the bean as a singleton, meaning that every time you request it, the same instance will be returned to you

    Notice that the singletons' scope is the enclosing container. Different container instances will have different singleton instances.

  • app.register_proto( bean_name, bean_function )

    Registers the bean as a template for creating beans, meaning that each time you request it, a different instance will be returned

In bean notation, you should pass a scope attribute with the value app.PROTOTYPE to use prototype scope. The default scope is app.SINGLETON.

Circular Dependencies

Since all dependencies are injected during creation of beans, not by setting properties, there is no support for circular dependencies in require-bean just yet.

Multiple Containers

var requireBean = require('require-bean');

/*Creates a container named 'myapp'*/
var myApp = requireBean.container('myApp');

/*Creates a container named 'otherapp'*/
var otherApp = requireBean.container('otherApp');

requireBean.container() method is called to create containers. Beans that are registered in one container will NOT be available in the other container instances. If your app starts other apps, you should consider create separated container instances for each app.

Interceptors

In order to keep it's core minimal, require-bean implements a system of interceptors, allowing third party code to hook into the bean resolution mechanism.

Features like registering all beans of a directory and the special dependency $return are implemented internally using interceptors.

An interceptor is easily created by extending a base Interceptor class, like the following:

var requireBean = require('require-bean'),
    Interceptor = requireBean.Interceptor;

function MyCustomInterceptor(){
}

Interceptor(MyCustomInterceptor);

Interceptor.prototype.bind = function(container){
/* this code gets called right after you register the interceptor in a container; */
}

Interceptor.prototype.doRegisterPhase = function(beanDef){
/*
    Whenever a bean is registered into the container, it's definition object will be passed here.
    beanDef usually will be an object with the bean notation format.
    return true to indicate that other interceptors should run after yours. Return false to break the chain
*/
    return true;
}

Interceptor.prototype.doPreparePhase = function(bean, callback, next){
/*
    Whenever a bean enters Preparation Phase (see details below), this will be called.
    You must call next passing three arguments: an error, the received bean and the received callback.
    If an error argument is passed, the chain will end in your interceptor, otherwise it will continue
    until it ends the Preparation Phase.
*/
    next(null, bean, callback);
}

Interceptor.prototype.doResolvePhase = function(bean, callback, next){
/*
    Whenever a bean enters Resolve Phase (see details below), this will be called.
    You should call next like in doPreparePhase, to continue or break the chain.
*/
    next(null, bean, callback);
}

Interceptor.prototype.doInstantiationPhase = function(bean, callback, next){
/*
    Whenever a bean enters Instantiation Phase (see details below), this will be called.
    You should call next like in doPreparePhase, to continue or break the chain.
*/
    next(null, bean, callback);
}

Bean Resolution Phases

Interceptors' processing will be easier to follow if we understand the phases of beans resolutions, which are:

  • Prepare Phase

    When the bean is selected to be resolved, it begins by calling it's Prepare Phase. This is the first step, and at this time, the interceptor's doPreparePhase method will be called, to give them a chance of doing some startup logic. If the Prepare Phase finishes successfully, the bean will move to Resolve Phase.

  • Resolve Phase

    This is the time when the bean checks its dependencies. Right now, all the interceptors will have a chance of running their doResolvePhase method. If no interceptor breaks the chain, the bean finally resolve its dependencies. For each unresolved dependency it begins the resolution process to resolve them all. When all dependencies are resolved, the bean moves to the Instantiation Phase.

  • Instantiation Phase

    Now the bean is ready to be instantiated, but once again, interceptors can hook it's behaviour. At this time, the interceptors will have a chance of running their doInstantiationPhase method. It is important that if any interceptor does the instantiation process by itself, that it sets the flag bean.instantiated to true, so it will know that it was instantiated during intercept hooks, thus not trying to create the bean object twice.

Finally, to hook up the interceptor into our container, we use the use method, like that:

var app = require('require-bean').container('myapp');
app.use( new MyCustomInterceptor() );

Out of the box, we provide a testing interceptor, that logs whenever a bean enters a resolution phase:

var middleware = require('require-bean').middleware;
app.use( middleware.debuggerInterceptor() );

Also, it's important to say that you should call use before registering any beans into the container.

Bean Object

The bean parameter that is passed to doRegisterPhase, doResolvePhase and doInstantiationPhase, looks supports the following operations:

/**********************************************************************
    Properties that should NOT be altered without a REALLY good reason
**********************************************************************/

bean.name;           // returns the name of the bean.
bean.dependencyList; // returns an object containing the dependencies.
bean.factory;        // returns the factory function to create the bean;


/**********************************************************************
    Method calls
**********************************************************************/
var dependencyList = bean.dependencyList;

// returns an object representing the next unresoved dependency of the bean.
var nextUnresolved = dependencyList.nextUnresolved();

// resolves the next unresolved dependency, by setting it to obj.
nextUnresolved.resolve( obj );

// applies a function passing the resolved dependencies as arguments. Returns what func returns.
dependencyList.apply( func );


/**********************************************************************
    Properties that can be altered
**********************************************************************/
bean.instantiated;   // If you instantiate the bean manually, this should be set to true.