Version 0.0.2, basic concept edition, there is A LOT to be done.
agna-disc (DISC) is a dependency injection (DI) service container written in javascript. The idea came from the dependency injection container of Spring framework (Java). You can read more about inversion of control and dependency injection containers here: http://www.martinfowler.com/articles/injection.html
There are two ways to create a DISC instance.
var _disc = require('agna-disc')(<configuration>); var _Disc = require('agna-disc');
var disc = new _Disc(<configuration>);Required: true Type: Object Plain javascript object with configuration parameters.
What is a plain javascript object?
var plainObject = {};
var plainObject = new Object();
var plainObject = Object();Any of the above.
The configuration structure describes the various components and their relations with each other within the DISC. The configuration itself is a plain javascript object with key and value pairs.
var configuration = {
allowOverwrite: false,
locator: <function>|<object>,
<component name>: <component config>,
<component name>: <component config>,
...
};Required: true Type: string The name of the component. It is used to designate a dependency or to get a component instance from the DISC. It is a unique identifier.
Required: true Type: Object Component configuration object.
There are a few reserved keys in the configuration.
Required: false Value: Boolean Default: false If set to true overwriting existing component definitions is allowed.
Require: false Value: Object Default: require Used by DISC to find and load component modules. It needs to be an object with a "__locate(path)" function.
Require: false Value: DISC instance The parent container.
There are three lifecycle states a component can have, however, not all are applicable to every component type.
A component enters the registered state when it's configuration is set.
A component enters the instantiated state when it's constructor has been called and the instance is stored.
A component enters the initialized state, when it's setters have been called and the instance is ready for use.
When you create a container providing a configuration object, all components in the configuration get registered. Components flagged for eager loading first get instantiated (all dependencies resolved and injected), then initialized. Same happens to their dependencies. This way entire dependency chains can get initialized. After initializing these components the DISC fires the INITALIZED event calling all listeners. When the listeners are done running, DISC is ready to use. You can get or set components.
There are four component types.
- Value
- Service
- Factory
- Scope
Defines a simple value component inside DISC. Value components are like variables in javascript.
var configuration = {
<component name>: {
type: 'value',
value: <value>
OR
callback: <function>
}
};You need to decide if you or a callback function will present the value. If the later is chosen, then the result of the function call will be stored as the value.
Required: true Value: 'value' Defines value component type.
Required: false Value: Anything Defines component value.
Required: false Value: Function Defines a callback function which will provide the component value. The callback function receives the container as a parameter.
The value type has two lifecycle state, registered and initialized.
var configuration = {
simple: {
type: 'value',
value: 'ample sample'
}
};
var disc = require('agna-disc')(configuration);
var simple = disc.get('simple');
console.log(simple);This will result in a console entry:'ample sample'
var configuration = {
simple: {
type: 'value',
callback: function (container) {
return 'sounds perfect!';
}
}
};
var disc = require('agna-disc')(configuration);
var simple = disc.get('simple');
console.log(simple);This will result in a console entry:'sounds perfect!'
Defines a service component inside DISC. Every service is instantiated and initialized only once and the same instance is returned when requested.
var configuration = {
<component name>: {
type: 'service',
module: <name>|<instance>
constructorArguments: <value>|[<value>, <value>], (not working for instance)
setter: {
[<property name>]|[<function name>]: <value>|<function with prop callback === true>
},
eagerLoad: true|false,
once: {
<event>: <functionName>|{method: functionName, arguments: <arg>|[<arg1>, <arg2>]}|[<functionName>|{method: functionName, arguments: <arg>|[<arg1>, <arg2>],...]
}
}
};Required: true Value: 'service' Defines service component type.
Required: true Value: String|Object Value could be the name of a module constructor, a reference to a constructor function or a module instance.
Required: false Value: Mixed|Array of mixed values Arguments to be passed to the constructor function.
Required: false Value: Object Defines setter method/property key/values pairs.
A key can be surrounded with brackets '[',']' in this case the value pair must be an array of values. It'll tell the DISC to execute the same setter multiple times on every array value. A key can point to a sub module property or a sub module setter function.
A value could be any valid javascript type or a function having a callback = true property. When the value is a function with having a callback = true property, it'll tell DISC to first execute the function and use it's return value as a parameter for the setter.
Required: false Value: Boolean Eager loads the component creating the component instance.
Required: false Value: Object On Container Event, a container even handler descriptor object with key/value pairs.
The key is the event name to handle.
The value defines the handler (single value) or handlers (array of values). The value could be:
- A string reference to a function.
- An object providing a reference to a function and the function arguments.
The service type has all three lifecycle states.
{
type: 'service',
module: 'koa'
}Is equivalent with:
var koa = require('koa');
return new koa();{
type: 'service',
module: '{!http.Agent}'
constructorArguments: 10
}Is equivalent with:
var http = require('http');
return new http.Agent(10);{
type: 'service',
module: '{!crypto.createCipher}'
constructorArguments: ['aes192', 'a password']
}Is equivalent with:
var crypto = require('crypto');
return new crypto.createCipher('aes192', 'a password');{
type: 'service',
module: 'koa'
setter: {
[use]: [function (next) {
...
}, ...]
}
}Is equivalent with:
var koa = require('koa')();
koa.use(function (next) {
...
});
koa.use(...);{
type: 'service',
module: 'koa'
once: {
'discContainerInitialized': {
method: 'listen',
arguments: [3000]
}
}
}It'll tell DISC to run
koa.listen(3000);when it's done with the initialization.
Defines a factory component in DISC. Every time the service is requested from DISC, it'll use the configuration to create an object instance defined by the configuration parameters.
var configuration = {
<component name>: {
type: 'factory',
module: <name>,
arguments: []
}
};Required: true Value: 'factory' Defines factory component type.
Required: true Value: String|Function Value could be the name of a module function, a reference to a module's function or a function instance.
Required: false Value: Mixed|Array of mixed values Arguments to be passed to the factory function.
The factory type has two lifecycle state, registered and initialized.
{
type: 'factory',
module: '{!crypto.createCipher}'
arguments: ['aes192', 'a password']
}Is equivalent with:
var crypto = require('crypto');
return new crypto.createCipher('aes192', 'a password');Besides the basic building blocks another important part of DISC is references. References are strings and help you with the configuration and with specifying dependencies.
There four types of references.
A simple reference does nothing more than refer to a component inside DISC. Usage:
{
<key>: '{@<component name>}'
}{
type: 'service',
module: 'koa'
once: {
'discContainerInitialized': {
method: 'listen',
arguments: [{@Port}]
}
}
}It'll tell DISC to run the following code when it's done with the initialization.
koa.listen(DISC.get('Port'));Binding reference is applicable to functions. DISC binds the parent component to "this" keyword inside the function. Usage:
{
<key>: '{*<component name>.<function name>}'
}{
type: 'service',
module: 'koa'
once: {
'discContainerInitialized': {
method: 'listen',
arguments: [{@Port}, {*Logger.log}]
}
}
}It'll tell DISC to run the following code when it's done with the initialization.
var logger = DISC.get('Logger');
koa.listen(DISC.get('Port'), logger.log.bind(logger));Callback reference is applicable to functions. The container will call the referenced function and use the return value for injection. Also, if the function is a property of a component, DISC binds the parent component to "this" keyword inside the function. Usage:
{
<key>: '{^<component name>.<function name>}'
}{
type: 'service',
module: 'koa'
setters: {
use: '{^Statmaker.getMiddleware}'
}
}Is equivalent with:
var statmaker = DISC.get('Statmaker');
koa.use(statmaker.getMiddleware());Locator reference tells the module locator to load a specific module, return with it, it's property or sub-property. Module name could include a path surrounded by parenthesis.
Usage:
{
<key>: '{!<module name>.<property>}'
}{
module: '{!winston.transports.Console}'
}Is equivalent with:
var winston = require('winston');
return winston.transports.Console);{
module: '{!(./mylibrary/MyWinston.test.js).transports.Console}'
}Is equivalent with:
var winston = require('./mylibrary/MyWinston.test.js');
return winston.transports.Console);##Tools Check out the jasmine tests for live examples!
- API documentation.
- Finishing all jasmine test.
- Make it more minifying friendly (X.prototype.function = ... vs var prototype = X.prototype; prototype.function = ...).
- Isomoprhic testing.
- Automatic building.
- Inline configuration besides configuration files.
- More appropriate naming (item?).
- Compiling, web package.
- Better error checking.
- Using _.get, where appropriate.
- Better events, simple event handling vs rubberduck.js?.
- Performance testing (CPU, Memory).
- How about ES6ifying? (consider isomorphic problems like babel "bloating", beacuse size does mater, speed does mater).
agna-disc is free software. It comes without any warranty, to the extent permitted by applicable law. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.