SDIC: Simple Dependency Injection Container
Dependency injection container with auto injection of module dependencies (by parameter names).
Prerequisites
Node.js version >= 6.0
Install
npm install sdic
Container initialization
const container = require('sdic').create();
or
import sdic from 'sdic';
const container = sdic.create();
Container API
load(path, opts = {})
Loads single module file or all module files in given path.
- path - path to module file or folder with modules
- opts - options
- alias - module alias (for a single file module) or a basedir alias (for a folder of modules)
- cache - keep instances of modules is cache? (default: true)
- tags[] - list of tags for loaded module(s)
- recursive - recursive loading of folder (default: true)
- filter - loads only modules by regexp filter
- ignore[] - list of regexp to ignore unwanted modules
- reverseName - create a module name "up to down" (default: false)
- prefix - prefix module name
- postfix - postfix module name
- deduplicate - remove multiple same string occurences (default: false)
- uppercaseFirst - create a module name with uppercased first letter (default: false - module name starts with lowercased letter)
- isConstructor - loaded module is a constructor, load and return it as a constructor, do return an instances (default: false - modules as singletons by default)
register(name, fn, opts = {})
Registers a single module.
- name - module name
- fn - module body (function, JSON, primitive, ...)
- options - see above
has(name)
Checks whether container contains given module by name.
get(name, overrides)
Returns registered module from container.
- name - registered module name
- overrides - collection of overridden module dependencies
getAll()
Returns all registered modules
getByTag(tag)
Returns all registered modules with given tag.
override(name, fn, opts)
Overrides registered module with new instance.
Parameters: see register method
unregister(name)
Unregisters module from container.
- name - module name to unregister
clear()
Removes all modules from container.
Tutorial
Module definition - a module with no dependencies
module.exports = () => {
return {
someMethod: () => {}
}
}
or using ES6 export default syntax:
export default () => {
return {
someMethod: () => {}
}
}
Module definition - a module with some dependencies
module.exports = (myDb, myNextService) => {
return {
someMethod: () => {
return myNextService.doSomething(myDb.getWhatever());
}
}
}
or using ES6 export default syntax:
export default (myDb, myNextService) => {
return {
someMethod: () => {
return myNextService.doSomething(myDb.getWhatever());
}
}
}
Multiple modules definition using ES6 named exports
export const firstFunctionalService = () => {
return {
method: () => ({passed: true})
}
};
export function secondFunctionalService (fooService) {
return {
method: () => fooService.method()
}
};
export class ClassService {
constructor(fooService) {
this.fooService = fooService;
}
method() {
return this.fooService.method();
}
}
ES6 note: container returns instances of modules by default (aka singletons). If you want to register a class (constructor function), you have to use option: isConstructor: true
class FooBar {}
container.register('FooBar', FooBar);
container.get('FooBar');
class FooBar {}
container.register('FooBar', FooBar, {isConstructor: true});
container.get('FooBar');
container.load('/path/to/constructors_folder', {isConstructor: true});
Manual module registration
container.register('myJson', {foo: 'bar'});
container.get('myJson');
container.register('myPrimitive', 123);
container.get('myPrimitive');
container.register('myDep', () => {
return {
doWhatever: () => {return 'whatever'}
}
});
container.register('myModule', (myJson, myPrimitive, myDep) => {
return {
getAll: () => ({myJson, myPrimitive, myDepValue: myDep.doWhatever()})
}
});
container.get('myModule');
Flat structure loading
Consider this project structure:
+ config.json
|
+ services/
+ - users.js
+ - user-roles.js
|
+ repositories/
+ - roles.js
+ - users.js
|
+ lib/
+ - validator.js
Let's load all files into SDIC.
container.load('./config');
container.load('./services');
container.load('./repositories');
container.load('./lib');
By default, all loaded modules will be named "down to up": camelCased filename + camelCased basedir. So the module names will be:
- config
- userRolesServices
- usersServices
- rolesRepositories
- usersRepositories
- validatorLib
The "plurals" sounds strange. We can rename the basedirs to "singular" or tweak the loader a little bit:
container.load('./config');
container.load('./services', {alias: 'service'});
container.load('./repositories', {alias: 'repository'});
container.load('./lib', {alias: null});
Now the module names will be:
- config
- usersService
- userRolesService
- rolesRepository
- usersRepository
- validator
Nice :-)
Nested structure loading
Now consider more nested project structure:
+ services/
+ - users/
+ -- roles.js
+ -- users.js
|
+ repositories/
+ - users/
+ -- roles.js
+ -- index.js
We'll skip "config" file and "lib" folder (see above). Let's load all files into SDIC.
container.load('./services');
container.load('./repositories');
By default, all loaded modules will be named "camelCased, down to up": filename + subfolders + basedir. So the module names will be:
- usersUsersServices
- rolesUsersServices
- indexUsersRepositories
- rolesUsersRepositories
Sounds really strange. Let's set up the loading better. The "services" folder first. It'd be cool to start the name with
the subfolder (if any), then the filename and the basedir at the end.
container.load('./services', {
alias: null,
reverseName: true,
postfix: 'service',
deduplicate: true
});
Now the modules will be:
- usersService
- usersRolesService
Better, but still sounds strange because of "plurals". To fix this we need to split the loading into two steps:
container.load('./services/users', {
alias: 'user',
postfix: 'service',
ignore: [/users\.js/]
});
container.load('./services/users/users', {alias: 'userService'});
Finally the modules will be:
- userService
- userRolesService
We can load the "repositories" folder the same way.
ES6 note: when loading named exports into the container, then:
- exported name will be taken instead of a filename (with lowercased first letter)
- filename will be taken for default (not-named) export
Minification
SDIC supports code minification. Because module dependencies are defined using parameter names, the code minification process would damage them (to a
, b
, c
, ... etc.) and SDIC would not be able to load them properly. To prevent this situation all you need to do, is to define the list of a module dependencies in a property dependencies
:
ES6 modules dependencies
const service = (a, b) => { // minified param names
return {
method: () => a.method() + b.method()
};
};
service.dependencies = ['fooService', 'barService']; // <--- definition of dependencies
export default service;
CommonJs modules dependencies
const service = (a, b) => { // minifies param names
return {
method: () => a.method() + b.method()
};
};
service.dependencies = ['fooService', 'barService']; // <--- definition of dependencies
module.exports = service;
Class dependencies
export class ClassService {
constructor(a, b) { // minified param names
this.fooService = a;
this.barService = b;
}
method() {
return this.fooService.method() + this.barService.method();
}
}
ClassService.dependencies = ['fooService', 'barService']; // <--- definition of dependencies
TODO
Based on the idea of: https://www.npmjs.com/package/adctd