Hi, I'm Mario Casciaro. I'm a Software Engineer and author of the book Node.js Design Patterns. I love travelling, science and hoppy craft beer. You can follow me on Twitter.

Dependency Injection in Node.js and other architectural patterns

The beauty of Node.js lies in its simplicity. But how does this simplicity work with large codebases and complex architectures? Is there anything we can do to try to keep our components well decoupled while managing easily their interconnections?

The dilemma

Designing a Node.js app was a totally new story for me (coming from a Java, C++, PHP background). For some aspects it was disorientating, no boundaries for defining a module, no need for complex hierarchies of classes, dependencies were as easy as requiring a file. It was wonderful at the beginning, a simple architecture is the ultimate source of happiness for a developer, but soon I realized that my code was slipping out of control, the more I was trying to make things modular, the more complex and “unusual” was the code I was writing. I needed something to bring again happiness in my Node.js programming.

The ideal solution

The first thing it came up to my mind was a Dependency Injection Container to achieve Inversion of Control (IoC), something like Spring or Guice, to take control of my modules. But the truth is that in Javascript, again, also DI is totally different and can be done in many different ways, some says a DI container is not even needed in Javascript. I would agree with that, it’s not as useful as in a language like Java, but I believe that offloading the wiring of your components to an external entity (the so called DI or IOC container), can open very interesting scenarios, especially in Javascript.

The second aspect to reach my programming zen was a service/hooks system. The ideal solution should allow to create extension points and let other modules attach their contributions in a decoupled way. Something like the Node.js event system but with the option to enforce order of execution and to allow async operations.

What’s out there

It looks like many other before me felt the need for a more powerful way to manage modularity in Node.js. Here is a (incomplete) overview of some of the most interesting.

Broadway

Broadway is part of the Flatiron arsenal and is an extremely simple and elegant architectural framework. Broadway allows to compose different plugins into its main app object, that can be considered in some ways a DI container. Each plugin can expose 3 methods: attatch, init, detatch. In Broadway a plugin looks like this:

1
2
3
4
5
6
7
8
var self = module.exports = {
attach: function(options) {
//'this' is a reference to the App object
this.helloWorld = function() {
console.log("Hello world!");
};
};
};

As you see, each plugin attaches his piece of features to the main Broadway App, in the case above the helloWorld function.

To load a plugin, you should do something like this:

1
2
3
4
var app = new broadway.App();
app.use(require("./helloworld");
app.helloWorld();

Beside being a dependency container, the Broadway App object inherits from EventEmitter2, thus offering a shared event bus across all the plugins. This can be easily exploited to implement a hooks system.
For more information about this and other features of Broadway please refer to this excellent blog post

Architect

Architect from Cloud9 is the plugin system at the heart of the Cloud9 IDE, so believe the authors when they say that is good to build large Node.js applications.
In Architect you define each module in a separate folder, containing a package.json file as if it was an npm module, containing beside other things, the services provided and consumed by the plugin.

1
2
3
4
5
6
7
8
{
"name": "helloWorld",
"main": "hello.js",
"plugin": {
"consumes": ["world"],
"provides": ["hello"]
}
}

A plugin in Architect looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = function setup(options, imports, register) {
// the consumed service
var world = imports.world;
register(null, {
// "hello" is the provided service
hello: {
say: function () {
console.log("Hello " + world)
}
}
});
};

As you see Architect is very easy to use and offers a solid dependency system, so solid that it will check the project’s dependency tree at “compile” time to identify inconsistencies (e.g. missing dependencies).

For more info about Architect take a look at its github page or at this nice presentation

Intravenous

Intravenous by Roy Jacobs is another interesting DI Container. Its interface is very close to the classical idea we have of dependency injection, and was influenced by the AngularJS DI. To define a module, just provide a factory function that accepts the dependencies as arguments, then add and extra properties to your exported module, to tell the container how to inject dependencies.

Here is an example module to show you what I’m talking about:

1
2
3
4
5
6
7
8
// file: /helloWorld.js
module.exports = function(hello) {
this.sayHello = function() {
console.log(hello.message);
}
};
module.exports.$inject = ["hello"];

The module, or service (as called by its documentation), can be then registered in the container:

1
2
3
// file: app.js
var container = intravenous.create();
container.register("helloWorld", require('helloWorld'));

The syntax is very clean, and beside the basic dependency injection, Intravenous gives some extra goodies:

  • If you need to create a new instance of your module every time, just add the Factory suffix to your dependency name, and a Factory object for that module will be created for you. You can then call get() to retrieve a new instance of the module.
  • When creating modules using Factories you can override dynamically its dependencies
  • You can have optional dependencies, using the ? suffix. If missing, null will be injected.
  • If you need extra isolation for the lifecycle of your modules, or want to override some dependencies in a particular context, just create a nested container.

Wire

Ok, now it comes the Swiss army knife. Wire, part of the cujoJS project. If you look at the features listed in the documentation, you will soon realize the extent of its functionality. With Wire you will have:

  • A DI Container with declarative syntax
  • Lifecycle management
  • Plugin architecture for extending Wire itself
  • Components, Proxies, Factories, Facets, AOP

Describing it here with a few examples would not give enough credit to it, so I encourage you to look at its documentation if you want to know more.

Seneca

The Seneca framework from Richard Rodger and the guys at NearForm, takes a quite radical approach, compared to the rest of the solutions out there. Seneca is a microservices framework where every functionality in your application is represented using a named service, resolved using a combination of properties.

1
2
3
4
5
6
seneca.act({salutation:'hello', to:'world'},
function(err,result) {
if( err ) return console.error( err )
console.log("Done: " + response.answer);
}
);

A service definition for the invocation above might look like this:

1
2
3
4
seneca.add({salutation:'hello'}, function(args,callback) {
console.log("Hello " + args.to);
callback(null, {answer: "I said hello"});
});

With this kind of approach a Seneca application will be organized in small self contained, composable services, putting the emphasis on “what” needs to be done, instead of worrying about how to structure code or what dependencies to use, and becomes very expressive when it comes to implement use cases.

For convenience, Seneca provides also a way to bind a set of services to an object, so that using them is less verbose and developer friendly:

1
2
3
4
var hello = seneca.pin({salutation:'hello', to:'*'});
hello.world({}, function(err,result) {
console.log(result);
});

Beside the core microservices framework, Seneca also provides an Active Record inspired data layer, plus a set of plugins ready to use to bootstrap a complete web application.

My alternative: Scatter

The history of Scatter started soon after I approached the Node.js world and it evolved while I was getting more insight of the Node.js philosophy and the current scenario of the architectural frameworks. I refactored it so many times, that I decided to publish only to force myself to not make any more big changes :).
It’s still interesting to see how it evolved, those are the milestones:

  1. At the beginning of time, Scatter was not even a DI container, it was a function iterating over the modules in a given directory to execute a given method on each one of them. Async execution was handled using Async
  2. Since I wanted to maintain some sort of logical order in the execution of those methods, I introduced dependencies in method execution, resembling the Async.auto function.
  3. The next step comes automatically, if I needed dependencies in service execution, then why not extending it to modules themselves? And what if instead of requiring a real path, I could refer modules using a “virtual” name, isolating the module from its location. The first version of the IoC container was born, where module names where extracted automatically from the .js file name, and dependencies where injected using the arguments names in the module factory function. No namespacing was supported.
  4. Then I wanted to introduce the ability to selectively inject different modules depending on the module who was requesting it. It was then the time for some dependency loop detection. All of this required a big revolution in the container internals.
  5. I wasn’t satisfied with the performances, so I started with some optimizations, and replaced Q with When.js for promises.
  6. Being still not satisfied of the whole API structure, I started the last big refactoring: back to basics. I introduced namespaces for modules and services, namespaces follows the directory structure for simplicity. Loop detection is handled by when.timeout (simpler and robust), I removed the ability to inject different dependencies based on the requesting module. All of this resulted in a great simplification of the internal architecture and a general improvement in the user-friendliness of the API, and from the other side introduced some very important features.

A basic Scatter module

1
2
3
4
5
6
7
8
9
10
11
module.exports = function(hello) {
return {
sayHello: function() {
console.log(hello.message);
}
}
};
module.exports.__module = {
args: ['salutation/hello']
}

You can see above, how a typical Scatter module looks like. In this example the module is instantiated using a factory and dependencies are injected in its argument. But that’s not the only way you have to link together your modules, in fact in Scatter you can also inject dependencies directly into the module properties or into the initialize function, and you can instantiate your module using factories, constructors or object literals (but also strings, arrays and synchronously or asynchronously). One of the goals of Scatter, in fact, is that there should not be a strict way of defining a module, but just gives the developer the freedom to use whatever is already possible today. One good to have side effect is that this way your module will work even without the Scatter container.

Properties Injection and Service as dependency

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var routes = module.exports = function(express) {
this.express = express;
}
routes.prototype.registerAllRoutes = function() {
return this.registerRoutes(this.express).then(function() {
console.log('All routes registered!')
}).otherwise(function(err) {
console.log('Ops...' + err.stack)
});
}
module.exports.__module = {
args: ['express/app'],
properties: {
registerRoutes: 'svc|sequence!routes/register'
},
provides: {
registerAllRoutes: {after: ['socketio']}
}
}

What is happening in the code above is the following:

  • Scatter, because of the non empty prototype will instantiate the module using new (constructor mode).
  • The module express/app is passed as argument of the constructor.
  • A property is injected into the instantiated module, in particular the property registerRoutes representing a service. When the service svc|sequence!routes/register is executed, it will resolve all the modules providing the service register under the namespace routes, and then will invoke all those resolved services one by one in sequence.
  • Exposes the method registerAllRoutes as a new service, and specifies that it should be executed after the same service is invoked over the module socketio.

Scatter services help decoupling the routes module from all the providers of the routes/register service. Using the Scatter services your modules don’t know of the existence of each other! On top of that, since the service is injected as dependency, unit testing the module it’s trivial task!

Why the name Scatter?

If you look at the way you initialize your Scatter container, you may get a tip on why:

1
2
3
4
5
var scatter = new Scatter();
scatter.registerParticles([
__dirname + '/plugins/*',
__dirname + '/core'
]);

Scatter indeed, will allow you to fragment your project across different root directories (your components roots, also called particles). By default Scatter does not require manual registration of your modules (although you can), but instead will scan your particles in search for your dependencies, using the module namespace to resolve the right directory. For example, you might have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/core
+-- app.js
+-- routes.js
+-- /express
+-- app.js
+-- /routes <--- Routes
+-- home.js
+-- profiles.js
/plugins
+-- /privateProfiles
+-- /routes <--- Routes
+-- profiles.js
+-- private.js
+-- /admin
+-- /routes <--- Routes
+-- admin.js

Ideally, if you design your modules properly, you should be able to enable/disable each plugin, by just adding/removing it from the plugins directory.

This is only the tip of the iceberg, to know more about Scatter, visit its github page.

Time to wrap up

Don’t worry we are close to the end of the post, I hope you enjoyed reading about Scatter, Architect, Broadway, Wire, Seneca, Intravenous, DI, IOC, AOP, Hooks, blah blah blah…and I invite you to find out more about how we can achieve better architecture and practically forge our own patterns on top of the awesome Node.js platform. Your feedback and contribution to the topic is greatly appreciated.

Updated 30.11.2013: Added the Seneca framework.