ZenInjector
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.jsvar dep1 = ;var dep2 = ;moduleexportsmyModuleName = 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 = ;var container = ;
Register a module with some dependencies:
container; // alternative syntaxcontainer;
Directly register a module with no dependency:
container; // it works great with `require` toocontainer;
Resolve a module with its dependencies:
container
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; // this also workscontainer;
Annotations
You still have to create the container:
var Zeninjector = ;var container = ;
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:
//@autoinjectmoduleexports { return dep1+dep2;};
With custom name and custom dependencies:
//@autoinject(name=myModuleName; dependencies=dep1,dep2)moduleexports { return a+b;};
This example has exactly the same effect as the previous one.
Automatically exports (similar to Zeninjector#registerAndExport
):
//@autoexportmoduleexportsmyConfigObject = env: 'test';
Asynchronous definitions
Let's have a look at an example where you need to connect to a database:
// file db.jsvar MongoClient = MongoClient;var db;moduleexports { ifdb process; else MongoClient; };
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 = MongoClient; // @autoinjectmoduleexports { // inject a Promise library var connect = Promise; return ;}
// @autoinjectmoduleexports { // 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 = ;Promise;
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 methodestrace
,debug
,info
,warn
,error
andfatal
.verbose
to add more logs with the levelDEBUG
. Defaults tofalse
.
.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:
moduleexports = //@autoinject {} //@autoinjectmoduleexports { return 'baz';}; //@autoinjectvar {}; //@autoinject {}; //@autoinject {};// /!\ this will raise an error if you try to require it// .scan's promise will be rejected here. {}; // will NOT be exported //@autoinjectmodule {};
License
MIT