A miniature, conventions-based IoC implementation for nodejs.
Background
After using angularjs for a while I became envious of its IoC facility and decided to create something for nodejs that delivered similar convenience on the server side.
minioc
is an IoC container.
At its core, minioc
is just a mapping between keys and values. It might be convenient to think of it as a hashtable, except that it behaves differently depending on how an item was put into it.
It understands 3 kinds of things:
- values - seems self explanitory
- factories - functions that produce values
- constructors - classes constructed on demand (depending on strategy)
When register
ing items with the container, the caller has the option of indicating how the container should resolve the item. The defaults are:
- values - always resolved as given
- factories - produce a value for each call
- constructors - construct a new instance for each call
var minioc = ; // a value...miniocasvalue"I'm a value"; // a factory...miniocas; // a constructor (class)... { this { return "I'm an instance created by constructor"; }} miniocas; // Print them all to the console...console;console;console;
Factories and Constructors as Values
Factories and constructors can be altered at the time of registration so that the result becomes the registered value rather than the target. This changes the defaults to:
- factories - invoked once with the result captured to fulfill all requests
- constructor - one instance created and captured to filfill all requests
var minioc = ; var factory_seed = 0 ctor_seed = 0; var { return "I'm produced by a factory with id: ";}; // a factory as a value...miniocfrom; // a constructor (class) as a value... { thisid = ctor_seed++; this { return "I'm an instance created by constructor with id: " ; };} miniocfrom; // write them to the console...console;console; // notice the values don't change...console;console;
Singleton = Immutable Registrations
Throughout a container's lifespan, items may be registered, unregistered, and modified. The exception is a singleton registration that already has a value.
// a singleton value...miniocassingletonvalue"value"; // a singleton factory...miniocassingleton; // a singleton value factory...miniocassingletonfrom; // a singleton constructor {}miniocassingleton; // a singleton value constructor...miniocassingletonfrom;
If a registration indicates it is for a singleton, the item may not be unregistered.
Injection
minioc
performs dependency injection on both factories and constructors. Its convention is to inject any argument whose name begins with a dollar sign ($); other named arguments must be caller-supplied.
The container distinguishes between items that it can fulfill entirely from those that are registered but not fully resolvable.
var minioc = expect = ; { thishas = typeof $data !== 'undefined'; thisdata = $data;} miniocas; // minioc has the registration...to; // ... but it can't fulfill the dependencies...to; var one = minioc; tohave;to; tohave;tobe; var data = something: "of interest" other: "data"; // The dependency must be user-supplied...var two = minioc; tohave;to; tohave;to; // Until the dependency can be met...miniocasvaluedata; var three = minioc; tohave;to; tohave;to;
There are several ways to instruct the container about dependencies:
- supply them during registration
- register them with the container (by convention)
- supply your own at the time of resolution.
Installation
$ npm install minioc
Example
var minioc = expect = ; // There is always a root container...var root = miniocroot;tobe; // When you've got a container, it has no registrations// except itself (container) and the root (root)... to;to; // You can register values... miniocasvalue4;to; var foo = what: 'foo' ;miniocasvaluefoo;to; // You can register factories, each #get will invoke// the factory and return the result... var { // within factories, `this` is bound to the container... return this;}; miniocas;to; // You can register factories that take arguments you// expect to be injected, I adopted the convention// used by angularjs: arguments beginning with `$`// will be fulfilled by the container... var { return $foo;} miniocas;to; // You may also supply your own arguments in place of those// that would have been injected... var bar = what: 'bar' ;var it = minioc;to; // You can register classes; each #get will invoke the// constructor and return the result... { thisname = name || 'unknown'; thisage = typeof age !== 'undefined' ? age : 'nunya';} miniocas; var first = minioc;tobe; var second = minioc;tobe; tonot; // You can initialize arguments to constructors by name... var info = name: 'Phillip' age: 'old enough to know better' ;var me = minioc; to;to;
Tests
Tests use mocha and expect.js, so if you clone the github repository you'll need to run:
npm install
... followed by ...
npm test
... or ...
mocha -R spec
API
!! WORK IN PROGRESS !!!
minioc
When you import the minioc
module, the resulting object is a constructor for the Container class, but it also provides several convenience methods that can be used directly.
can
- property- determines whether an item can be resolved with all dependencies.create
- function - creates a new nested container.get
- function - gets an item according to its registration with the root container.has
- function - determines if an item has a registration with the root container.register
- function - registers a named item with the root container.fulfill
- function - registers a callback function with the root container to be invoked when the function's arguments can be fulfilled.root
- property - provides access to the root container.when
- function - registers a callback invoked when an item can be resolved with all dependencies.
Container
Containers are nested. The root container always exists and most registrations will be made there. Nested containers provide isolation boundaries where registrations can be specialized (overriden).
constructor
Container's constructor takes an optional argument, next
, which indicates where the container is nested. Containers form a chain back to the root
container, so if next
is omitted, it will automatically be chained to root
.
behavior
can
- property - determines if the container can resolve an item with all dependencies.get
- function - gets an item according to its registration.has
- function - determines if an item has a registration.register
- function - registers a named item and returns itsRegistration
.fulfill
- function - registers a callback function to be invoked when the function's arguments can be fulfilled.when
- function - registers a callback invoked when an item can be resolved with all dependencies.
Registration
Registrations are used to instruct the container as to how each registered object should be treated. It establishes the behavior related to each.
Function Descriptions
get: gets an item registered with the container
var it = minioc;
If you're getting something that takes arguments, you may fulfill those arguments, overriding any injected or configured values.
var it = minioc;
has: determines if the container can fulfill requests for an item
if minioc console;
register: registers an item with the container.
// as a bare-value...miniocasvalue; // ... or as a factory ...miniocas; // ... or as a ctor ...miniocas;
fulfill: shedules a callback function to be invoked as soon as all of its dependencies can be met.
// an identifying name must be provided with a fulfillment callback;// this example indicates it is dependent on `config`, so minioc will// invoke the callback as soon as it can inject the `config`// dependency...minioc;
when: provides a callback to be invoked when a particular, named item gets registered.
minioc
Usage
require
minioc:
var minioc = ;
Obtaining a Container
Once imported, minioc
can be used as the root container.
var minioc = expect = ; tohave;tohave;tohave;tohave;tohave;tohave;
Registering Bald Values
Any javascript object can be placed in the container by name and retrieved later.