1.3.1 • Published 9 years ago

jambalaya v1.3.1

Weekly downloads
2
License
ISC
Repository
github
Last release
9 years ago

Jambalaya

Jambalaya is an IoC container for node.js. The IoC configuration can be defined as pure JSON or in JavaScript with functions to be executed.

Full Example

Configuration

{
    "dbConnection": {
        "type": "libs/db/connection", // libs/db/connection's exports is set to the constructor function
        "construct": [
            "localhost",
            "myuser",
            "mypass"
        ]
    },
    "entityDao": {
        "type": "EntityDao@libs/dao/entity",
        "construct": [
            "@dbConnection"
        ],
        "factory": true
    },
    "otherDao": {
        "type": "OtherDao@libs/dao/other",
        "construct": [
            "@dbConnection"
        ],
        "factory": true
    },
    "myService": {
        "type": "MyService@libs/service/my",
        "construct": [
            "@entityDao",
            "someArg"
        ],
        "post-construct": [
            {"property": myOtherService, "@myOtherService"}
        ]
    },
    "myOtherService": {
        "type": "MyOtherService@libs/service/other",
        "construct": [
            "@otherDao",
            456
        ],
        "post-construct": [
            {"property": myService, "@myService"}
        ]
    },
    "app": {
        "type": "Application@libs/app",
        "construct": [
            "@myService",
            "@myOtherService"
        ]
    }
}

The @ symbol is used to refer to other objects in the IoC container.

Application Code

// require Jambalaya and our config file
var Jambalaya = require('jambalaya'),
    config = require('./config.json');

// create our container & get our application object
var container = new Jambalaya(config, {root: __dirname}),
    app = container.get("app");

app.run();

Creating and Using Containers

To create a container, we create a new Jambalaya instance using the configuration and an optional root path for requiring modules, eg:

var container = new Jambalaya(config, {root: __dirname});

Getting objects from a container is just as straightforward:

var myObj = container.get("myObjName");

If you need to dynamically change the contents of the IoC container, you can use either the set(name, obj) method or the setFromConfig(name, config) method. The first method will add obj with the key name. The second method will add an object using the IoC configuration for a single object. With the second method, you can create factories.

Creating IoC Configuration

Specifying Object Types

Object types are specified with the "type" property. The property must contain a path to the module, and can optionally contain a property name of the module's exports.

If the property only contains a path (eg, "/path/to/module"), the module is used as the object's constructor. In other words:

(obj instanceof require('/path/to/module')) === true

If the property contains a name and a path, in the format of "MyPropertyName@/path/to/module", then the value exported with the key "MyPropertyName" is used as the object's constructor. In other words:

(obj instanceof require('/path/to/module').MyPropertyName) === true

Configuring Constructors

The constructor arguments to use when creating an object are supplied via the "construct" property. This property can either be an array of constructor arguments or a function that performs the object construction.

As a function, it might look like:

{
    "type": "MyType@mymodule",
    "construct": function (MyType, container) {
        return new MyType(container.get("myDependency"));
    }
}

As an array, it might look like:

{
    "type": "MyType@mymodule",
    "construct": [
        "@myDependency",
        "my other value"
    ]
}

The "@" symbol is used to reference other objects in the container. If you need to use a string that starts w/ "@", use a backslash to escape it. For example: "\\@".

Note: Only the first character needs to be escaped if it has a "@". If it's not before the first character, the backslash is not removed, so "someone\\@myemail.com" will not be modified.

Dependencies are resolved recursively in constructor arguments, so if you supply an object or array with "@..." property values, those values will be resolved, eg:

{
    "type": "MyType@mymodule",
    "construct": [
        {
            "prop1": "@myDependency",
            "prop2": [
                "@myOtherDependency"
            ]
        },
        [
            "prop3": "@myThirdDependency"
        ]
    ]
}

Post-construction

To execute code after an object is created, use the "post-construct" property. This property should be set to a function to execute or an array of objects describing what to do.

As a function, it might look like:

{
    "type": "MyType@mymodule",
    "post-construct": function (instance, container) {
        instance.dependency = container.get("@myDependency");
    }
}

As an array, it might look like:

{
    "type": "MyType@mymodule",
    "post-construct": [
        {"property": "serverConnection", "value": "@serverConnection"},
        {"method": "connectToServer", "args": ["@myDependency", "someValue"]}
    ]
}

If the element of the array contains the "property" property, the container will set a property of the object using the specified "value".

If the element of the array contains the "method" property, the container will invoke the method specified with the args specified.

Both property values and method arguments are handled like constructor arguments in that dependencies are resolved and resolved recursively. So you can refer to other objects in the container using the @ symbol.

Post-construction is handled after ALL objects in a container are created, so it can be used to handle cycles in your object graph.

Factories vs Singletons

By default, object's are considered singletons. This means there is only ever one instance of the object in memory, every reference to the object refers to this one instance.

If you set the "factory" property to true in an object's config, a new instance of the object will be created every time the object is accessed.

Autowiring

By default, Jambalaya containers require that object configurations specify the constructor arguments to use. You can, however, enable autowiring to automatically set constructor arguments.

To enable autowiring, set the autowire option like so:

var container = new Jambalaya(config, {root: __dirname, autowire: true});

The autowiring feature will treat the constructor arguments of a type as the names of DI objects, so the following function:

function EntityDao(dbConnection) {
    this.dbConnection = dbConnection;
}

will be insantiated as if the construct: config was set to ["@dbConnection"].

When autowiring is enabled, containers will also try to create objects even if no DI config exists for the object. In this case, the name of the object will be treated as a type specifier (eg, "MyType@mymodule").

Autowiring Hints

In more complex applications, constructor parameter names cannot be used to reference DI objects, simply because there are so many objects that you'd end up running out of unique parameter names. For such applications, it is also possible to map constructor parameters to their default DI config values through autowiring hints.

To add autowiring hints, use the Jambalaya.inject() function like so:

var Jambalaya = require('jambalaya');

function EntityDao(dbConnection) {
    this.dbConnection = dbConnection;
}

Jambalaya.hint(EntityDao, {
    dbConnection: "Connection@my/mysql/connection"
});

function OtherEntityDao(dbConnection) {
    this.dbConnection = dbConnection;
}

Jambalaya.hint(OtherEntityDao, {
    dbConnection: "Connection@my/postgres/connection"
});

construct: specifiers will still override autowiring hints, so custom values can still be injected.

Merging and Extending DI Configuration

If you have an application that you want to make extensibile, you can use Jambalaya to provide that extensibility. You could gather multiple DI configs, some from user provided modules (ie, plugins), and merge them together by providing all of them to the container constructor. For example:

var Jambalaya = require('jambalaya');

var configBase = require('app/base.config.json'),
    configPlugin1 = require('app/plugins/plugin1/config.json'),
    configPlugin2 = require('app/plugins/plugin2/config.json');

var container = new Jambalaya([configBase, configPlugin1, configPlugin2]);

If you want to provide arrays in your DI config that can be extended, Jambalaya can help you too. You can define a base array in your base JSON, then extend it by using the '+' prefix character in your JSON configs that should be merged. For example,

Base config:

{
    "myStuff": [
        1, 2, 3
    ]
}

Plugin config:

{
    "+myStuff": [
        4, 5
    ]
}

In the resulting container, the object with the key "myStuff" would be an array containing [1, 2, 3, 4, 5].

1.3.1

9 years ago

1.3.0

9 years ago

1.2.0

9 years ago

1.1.1

9 years ago

1.1.0

9 years ago

1.0.0

9 years ago