Dependency Injection in Node.js
Erin Noe-Payne
Who here has...
- has built a node app with more than 1 file?
- written that one giant file...?
- writes unit tests against their code?
Why do we care about DI?
- Promotes good design
- Single Responsibility, Open / Closed principles
- Unit testing
What's out there?
Hijack `require`
"use strict";
require('../example-utils').listModuleAndTests(__dirname + '/foo.js', __filename);
var proxyquire = require('../..')
, assert = require('assert')
, pathStub = { };
// when not overridden, path.extname behaves normally
var foo = proxyquire('./foo', { 'path': pathStub });
assert.equal(foo.extnameAllCaps('file.txt'), '.TXT');
// override path.extname
pathStub.extname = function (file) { return 'Exterminate, exterminate the ' + file; };
// path.extname now behaves as we told it to
assert.equal(foo.extnameAllCaps('file.txt'), 'EXTERMINATE, EXTERMINATE THE FILE.TXT');
// path.basename and all other path module methods still function as before
assert.equal(foo.basenameAllCaps('/a/b/file.txt'), 'FILE.TXT');
console.log('*** All asserts passed ***');
IoC Container
var di = require('di');
var Engine = require('./engine');
var Car = function(engine) {
this.engine = engine;
};
Car.prototype = {
run: function() {
this.engine.start();
},
isRunning: function() {
return this.engine.state === 'running';
}
};
di.annotate(Car, new di.Inject(Engine));
module.exports = Car;
So what are we using?
- nject
- express-train
Nject
var nject = require('nject');
var tree = new nject.Tree();
tree.constant('a', 7);
tree.constant('b', 9);
tree.register('sum',
/*
variable names matter!
a will be injected with nject's registered constant a
b will be injected with constant b
*/
function(a, b) {
return a+b;
});
tree.register('asyncDifference',
/*
this module resolves asynchronously,
because it is injected with the reserved word _done
*/
function(a, b, _done) {
setTimeout(function(){
_done(b-a)
}, 1000)
});
tree.register('printer',
function(sum, asyncDifference) {
console.log(asyncDifference);
console.log(sum);
});
tree.resolve(function(err, resolved){
//all are true...
resolved.a == 7
resolved.b == 9
resolved.sum == 16
resolved.asyncDifference == 2
resolved.printer == undefined
});
// console logs from printer:
// 2
// 16
Express-train
//index.js
train = require('express-train')
tree = train({
base : __dirname,
files : [
'**/*.js',
'!{public, views, test}/**'
]
})
tree.resolve()
//models/Users.js
var mongoose = require('mongoose');
/* This module has no dependencies */
module.exports = function () {
var UserSchema = new mongoose.Schema({
username:{ type:String, required:true, unique:true },
email:{ type:String, required:false, unique:false },
password:{ type:String, required:true}
});
/* This return value is what will be injected when Users is referenced */
return mongoose.model('users', UserSchema);
}
Yeah, yeah, but what does it REALLY look like?
Lessons Learned
- Unique file names...
- To DI, or not to DI?
- Async resolution - worth it?
Questions?
Dependency Injection in Node.js
By autoric
Dependency Injection in Node.js
- 1,222