Dependency Injection in NodeJS with Forge and Smithy

Who needs dependency injection in JavaScript?

Coming from the .NET community, I am well versed in the benefits of using an Inversion of Control container. This really allows you to focus on the single responsibility principle and makes unit testing easier. For a while now I have been working with NodeJS and learning its patterns and practices. Many argue the use of an IoC in a dynamic language like JavaScript is unnecessary since you can take advantage of duck typing.

Existing frameworks

While I haven't been writing NodeJS applications for long, I already miss the presence of an IoC. I started looking into this even more after using AngularJS. AngularJS is a great client side framework and is built around registering components within modules and handles resolving of dependencies automatically based on function parameter names or named dependency hints in a array. After looking further I found that Vojta Jina has created a NodeJS library for this called node-di. This is very similar to how angular works, as it should be, since Vojta is also one of the main engineers behind angular.

It works relatively well but didn't feel as fluid or as familiar to what I am used to using. Then, I notice Nate Kohari has also recently started a new NodeJS project called forge. If you aren't familiar with Nate already, he was the original creator of ninject, an IoC container for .NET. I was immediately excited about this when I saw it as I have used ninject on projects before and knew it would feel more familiar and fluid to me coming from a .NET background.

Forge is pretty young at this point. Nate is open about the fact that it is early in its development and probably not production-ready. It is still something you can pull down and play with and Nate is contributing to it frequently.


So what is Smithy?

As I said, I liked what I saw with forge right away. As Nate said, it is young and evolving. I am heavily using TypeScript and so I initially started writing TypeScript declarations for Forge. I realized it doesn't quite have the extensibility of registering separate modules for bootstrapping that I am used to. I like to keep module registration separated into logical groupings and closer to their types rather than in a central location. That is the goal of Smithy.

How do I get it?

npm install forge-di-smithy

  • Equipment
    An array of tools that can be ready by the Blacksmith. blacksmith.registerEquipment(equipment);
  • Blacksmith
    The main container that will wrap a forge instance and register equipment to bindings on the forge instance. new Smithy.Blacksmith(forge);
  • Tools
    A tool is a mapping for a binding. There are 3 different types of tools. Each tool requires a name and a target to be passed as the first 2 arguments respectively. After that, you can pass in a lifecycle, hint, binding arguments, and a when predicate. These are currently configured in 9 different overloads. Will refactor these out into an options object soon.
    UPDATE! [2014.04.20]
    I have refactored out the overloads into a options object. This options object follows the following contract.

    { 
        name: string; 
        target: T; 
        lifecycle?: Lifecycle; 
        when?: Forge.IPredicate; 
        hint?: string; 
        bindingArguments?: Forge.IBindingArguments;
    }
    
    • Type Tool
      The type tool is a mapping to the forge.bind(...).to.type(...); registration.

    • Function Tool
      The function tool is a mapping to the forge.bind(...).to.function(...); registration.

    • Instance Tool
      The instance tool is a mapping to the forge.bind(...).to.instance(...); registration.


Example

Here is a brief example. Best example can be found by going to specs/lib/

TypeScript

import Forge = require('forge-di');  
import Smithy = require('forge-di-smithy');

class Foo { }  
class Foo2 extends Foo { }  
class Bar {  
  constructor(foo: Foo) {...}
}
class Blah {  
  constructor(public dependency: Foo) {
    "dependency->foo2";
  }
}

var equipment: Smithy.IEquipment = [  
  new Smithy.Tools.Type({name: 'foo', target: Foo}),
  new Smithy.Tools.Type({name:'foo2', target: Foo2}),
  new Smithy.Tools.Type({name: 'bar', target: Bar}),
  new Smithy.Tools.Type({name: 'blah', target: Blah})
];

...

var forge = new Forge();  
var blacksmith = new Blacksmith(forge);

blacksmith.registerEquipment(equipment);

...

var boo = forge.get('foo');  
var blah = forge.get('blah');

expect(boo).to.be.an.instanceOf(Foo);  
expect(blah).to.be.an.instanceOf(Blah);  
expect(blah.dependency).to.be.an.instanceOf(Foo2);  

JavaScript

var Forge = require('forge-di');  
var Smithy = require('forge-di-smithy');

function Foo () { }  
function Foo2 () { }  
function Bar (foo) {  
  this.foo = foo;
}
function Blah (dependency) {  
  "dependency->foo2";
  this.dependency = dependency;
}

var equipment = [  
  new Smithy.Tools.Type({name: 'foo', target: Foo}),
  new Smithy.Tools.Type({name:'foo2', target: Foo2}),
  new Smithy.Tools.Type({name: 'bar', target: Bar}),
  new Smithy.Tools.Type({name: 'blah', target: Blah})
];

...

var forge = new Forge();  
var blacksmith = new Blacksmith(forge);

blacksmith.registerEquipment(equipment);

...

var boo = forge.get('foo');  
var blah = forge.get('blah');

expect(boo).to.be.an.instanceOf(Foo);  
expect(blah).to.be.an.instanceOf(Blah);  
expect(blah.dependency).to.be.an.instanceOf(Foo2);  
comments powered by Disqus