jambalaya
v1.3.1
Published
Configuration based IoC container for node.js.
Downloads
11
Maintainers
Readme
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]
.