Iniettore

TODO
Table of Content
Features
Extreme late binding
With exception of eager singletons, all instances and dependencies are resolved only when requested rather when registered into the container.
Functional Programming support
FUNCTION
, PROVIDER
and INSTANCE
mappings are the ideal solution to have DI in Functional Programming.
Lifecycle management
iniettore provides containers and singleton lifecycle management.
Predictable
iniettore handles all operation in a syncronous way so at any point in time you know what is instanciated and what is not.
ECMA Script 5 required features
iniettore assumes that the following ES5 features are available. If you want to use the library in a no-ES5 compatible environment please provide a polyfill. For example see es5-shim.
Object.create
Function.prototype.bind
Quick start
Installation
npm install iniettore --save
Simple usage
import iniettore from 'iniettore'
import { VALUE, LAZY, SINGLETON, CONSTRUCTOR } from 'iniettore/lib/options'
class UltimateQuestion {
constructor(answer) {
console.log(answer)
}
}
var container = iniettore.create(function (context) {
context
.map('answer')
.to(42)
.as(VALUE)
.map('question')
.to(UltimateQuestion)
.as(LAZY, SINGLETON, CONSTRUCTOR)
.injecting('answer')
})
var question = container.get('question')
console.log(question instanceof UltimateQuestion)
Terminology
- Container - TBC
- Context - TBC
- Mapping - TBC
- Creational mapping - TBC
Advanced usage
Values and instances
import iniettore from 'iniettore'
import { VALUE, INSTANCE } from 'iniettore/lib/options'
var drone = {
fly: function () { }
}
var container = iniettore.create(function (context) {
context
.map('answer').to(42).as(VALUE)
.map('drone').to(drone).as(INSTANCE)
})
var answer = container.get('answer')
console.log(container.get('drone') === drone)
Functions
You can register a function into the container and specify the its dependencies. When requesting the function you will get a partial application of it with all dependencies already satisfied.
import iniettore from 'iniettore'
import { VALUE, FUNCTION } from 'iniettore/lib/options'
function fooFunction(bar, baz) {
console.log(bar, baz)
}
var container = iniettore.create(function (context) {
context
.map('bar').to('BAR').as(VALUE)
.map('foo').to(fooFunction).as(FUNCTION).injecting('bar')
})
var foo = container.get('foo')
foo(42)
Providers
Providers are generic functions that returns object or values specific for your application domain. A factory function can be seen as a special use case of the provider pattern.
Every request will invoke the provider function and return a new value. The returned value depends on the nature of the registered provider function.
import iniettore from 'iniettore'
import { PROVIDER } from 'iniettore/lib/options'
var idx = 0
function fooProvider(bar) {
idx++
return { idx, bar }
}
var container = iniettore.create(function (context) {
context
.map('bar').to(42).as(VALUE)
.map('foo').to(fooProvider).as(PROVIDER).injecting('bar')
})
console.log(container.get('foo'))
console.log(container.get('foo'))
Constructors
You can register constructors specifying the constructor dependencies. Every request will receive a new instance of the specified constructor.
Note: no setters injection is supported at the moment.
import iniettore from 'iniettore'
import { CONSTRUCTOR } from 'iniettore/lib/options'
var idx = 0
class Bar {
constructor() {
this.idx = ++idx
}
}
var container = iniettore.create(function (context) {
context
.map('bar').to(Bar).as(CONSTRUCTOR)
})
console.log(container.get('bar'))
console.log(container.get('bar'))
Child containers
Containers can be organized in a hierarchy. Given a container you can create a child container invoking container.createChild(configure :Function) :Object
and providing the configuration function.
A child container can make use all the mappings of the parent container and ancestor containers.
import iniettore from 'iniettore'
import { VALUE, PROVIDER } from 'iniettore/lib/options'
function fooProvider(bar, baz) {
return { bar, baz }
}
var parent = iniettore.create(function (context) {
context
.map('bar').to(42).as(VALUE)
.map('baz').to('pluto').as(VALUE)
})
var child = parent.createChild(function (context) {
context
.map('bar').to(84).as(VALUE)
.map('foo').to(fooProvider).as(PROVIDER).injecting('bar', 'baz')
})
console.log(parent.get('bar'))
console.log(child.get('bar'))
console.log(child.get('foo'))
Blueprints
Blueprint is effectively a convenient way to register a child container factory. The mapping value is the configuration function for the child container. Every time you request the blueprint alias you will get a new child container.
import iniettore from 'iniettore'
import { VALUE, BLUEPRINT } from 'iniettore/lib/options'
function configureChildContext(context) {
context
.map('baz').to('pluto').as(VALUE)
}
var container = iniettore.create(function (context) {
context
.map('bar').to(42).as(VALUE)
.map('foo').to(configureChildContext).as(BLUEPRINT)
})
var child = container.get('foo')
console.log(child.get('bar'))
console.log(child.get('baz'))
In case you are interested in only one mapping in the child container you can specify the exported alias. See example below.
import iniettore from 'iniettore'
import { VALUE, FUNCTION, BLUEPRINT } from 'iniettore/lib/options'
function baz(bar) {
console.log(bar)
}
function configureChildContext(context) {
context
.map('baz').to(baz).as(FUNCTION).injecting('bar')
}
var container = iniettore.create(function (context) {
context
.map('bar').to(42).as(VALUE)
.map('foo').to(configureChildContext).as(BLUEPRINT).exports('baz')
})
var baz = container.get('foo')
console.log(baz())
Transient dependencies
EXPERIMENTAL FEATURE: don't abuse of it.
While requesting an alias it's possible to provide temporary dependencies to satisfy dependencies of the requested mapping or one of his dependency.
Note: Transient dependencies cannot be used to satisfy dependencies in the ancestor containers.
import iniettore from 'iniettore'
import { VALUE, PROVIDER } from 'iniettore/lib/options'
function fooProvider(bar, baz) {
return { bar, baz }
}
var container = iniettore.create(function (context) {
context
.map('bar').to(42).as(VALUE)
.map('foo').to(fooProvider).as(PROVIDER).injecting('bar', 'baz')
})
var transientDependencies = {
baz: 'pluto'
}
var foo = container.using(transientDependencies).get('foo')
console.log(foo)
Service locator
TBC
Singletons
Constructors and Providers can also be marked as singletons. A function registered as SINGLETON, PROVIDER
will be used as singleton instance factory. A constructor registered as SINGLETON, CONSTRUCTOR
will be used to create only once instance of the constructor type.
Singletons can be marked as: LAZY
, EAGER
or TRANSIENT
.
Lazy singletons
A mapping marked as LAZY, SINGLETON
produce a singleton instance that gets created at the first time it is requested. It gets destroyed only when the container itself is destroyed. See container.dispose
.
Lazy Singleton Provider
import iniettore from 'iniettore'
import { LAZY, SINGLETON, PROVIDER } from 'iniettore/lib/options'
var idx = 0
function fooProvider() {
return {
id: ++idx
}
}
var container = iniettore.create(function (context) {
context
.map('foo').to(fooProvider).as(LAZY, SINGLETON, PROVIDER)
})
var foo1 = container.get('foo')
var foo2 = container.get('foo')
console.log(foo1)
console.log(foo1 === foo2)
Lazy Singleton Constructor
import iniettore from 'iniettore'
import { LAZY, SINGLETON, CONSTRUCTOR } from 'iniettore/lib/options'
var idx = 0
class Bar {
constructor() {
this.idx = ++idx
}
}
var container = iniettore.create(function (context) {
context
.map('bar').to(Bar).as(LAZY, SINGLETON, CONSTRUCTOR)
})
var bar1 = container.get('bar')
var bar2 = container.get('bar')
console.log(bar1)
console.log(bar1 === bar2)
Eager singletons
A mapping marked as EAGER, SINGLETON
gets created at registration time.
All the required dependencies must be already registered in the current container or in one of its ancestors.
Eager singletons gets destroyed when the corresponding container is destoroyed.
import iniettore from 'iniettore'
import { EAGER, SINGLETON, PROVIDER, CONSTRUCTOR, VALUE } from 'iniettore/lib/options'
var idx = 0
function fooProvider(answer) {
console.log('foo provider invoked:', answer)
return {
id: ++idx
}
}
class Bar {
constructor(answer) {
console.log('Bar instance created', answer)
}
}
var container = iniettore.create(function (context) {
context
.map('answer')
.to(42)
.as(VALUE)
.map('foo')
.to(fooProvider)
.as(EAGER, SINGLETON, PROVIDER)
.injection('answer')
.map('bar')
.to(Bar)
.as(EAGER, SINGLETON, CONSTRUCTOR)
.injection('answer')
})
Transient singletons
A mapping marked as TRANSIENT, SINGLETON
produce a temporary lazy singleton instance. The instance gets created at the first time it is requested (directly or as dependency of another mapping) and gets destroyed when is not used anymore.
A transient singleton allows to gurantee that at any given point in time there are no more than one instance of the respective mapping (whetever has been creates using a constructor or a provider function).
In order to announce that a singleton is not used anymore you can invoke container.release(name :string) :void
method. The instance gets released (i.e. all references to it gets removed) when container.release
is invoked as many time as it has been requested. See examples below.
Transient Singleton Provider
import iniettore from 'iniettore'
import { TRANSIENT, SINGLETON, PROVIDER } from 'iniettore/lib/options'
var idx = 0
function fooProvider() {
return {
id: ++idx
}
}
var container = iniettore.create(function (context) {
context
.map('foo').to(fooProvider).as(TRANSIENT, SINGLETON, PROVIDER)
})
var foo1 = container.get('foo')
var foo2 = container.get('foo')
console.log(foo1 === foo2)
container.relase('foo')
var foo3 = container.get('foo')
console.log(foo1 === foo3)
Transient Singleton Constructor
import iniettore from 'iniettore'
import { TRANSIENT, SINGLETON, CONSTRUCTOR } from 'iniettore/lib/options'
var idx = 0
class Bar {
constructor() {
this.idx = ++idx
}
}
var container = iniettore.create(function (context) {
context
.map('bar').to(Bar).as(TRANSIENT, SINGLETON, CONSTRUCTOR)
})
var bar1 = container.get('bar')
var bar2 = container.get('bar')
console.log(bar1 === bar2)
container.relase('bar')
var bar3 = container.get('bar')
console.log(bar1 === bar3)
Transient singleton dependencies
import iniettore from 'iniettore'
import { TRANSIENT, SINGLETON, CONSTRUCTOR, PROVIDER } from 'iniettore/lib/options'
var idx = 0
class Bar {
constructor() {
this.idx = ++idx
}
}
function fooProvider(bar) {
return {
bar,
method: function () {}
}
}
var container = iniettore.create(function (context) {
context
.map('bar').to(Bar).as(TRANSIENT, SINGLETON, CONSTRUCTOR)
.map('foo').to(fooProvider).as(TRANSIENT, SINGLETON, PROVIDER).injecting('bar')
})
var foo1 = container.get('foo')
console.log(foo1)
var foo2 = container.get('foo')
console.log(foo1 === foo2)
console.log(foo1.bar === foo2.bar)
container.relase('foo')
container.relase('foo')
var foo3 = container.get('foo')
console.log(foo3)
console.log(foo1 === foo3)
console.log(foo1.bar === foo3.bar)
Lifecycle
iniettore offers a simple concept of lifecycle management for singleton instances and containers. Let's see what it means for instances and containers.
instance.dispose
Given a LAZY, SINGLETON
or TRANSIENT, SINGLETON
instance that implements a method called dispose() :void
when the instance gets released the container will invoke it. This allow you to cleanup any hanging reference (e.g. remove event listeners) so the instance can properly garbage collected.
import iniettore from 'iniettore'
import { LAZY, SINGLETON, CONSTRUCTOR } from 'iniettore/lib/options'
import { EventEmitter } from 'events'
class Foo {
constructor(events) {
this._events = events
this._events.on('message', this._onMessage)
}
_onMessage(evt) { }
dispose() {
this._events.off('message', this._onMessage)
}
}
var container = iniettore.create(function (context) {
context
.map('events').to(EventEmitter).as(EAGER, SINGLETON, CONSTRUCTOR)
.map('foo').to(Foo).as(LAZY, SINGLETON, CONSTRUCTOR).injecting('events')
})
var events = container.get('events')
console.log(events.listeners('message').length)
var foo = container.get('foo')
console.log(events.listeners('message').length)
container.release('foo')
console.log(events.listeners('message').length)
container.dispose
TBC
Troubleshooting
TBC