Nodejitsu

Save time managing and deploying your node.js app. Code faster with jitsu and npm

Inversion of Control and Dependency Injection with Broadway

About the author

Name
Location
Worldwide
nodejitsu nodejitsu

Over the last few months I've been using flatiron and by extension broadway.

I like broadway a lot, so I'd like to take this opportunity to explain how it works and why you should be using it.

Here are a few phrases and bits of jargon that tend to pop up when discussing broadway:

  • Inversion of Control
  • Dependency Injection / Feature reflection
  • Application extensibility

In this post, I'm going to explain what the mean, how they work in broadway, and why they're a good thing.

More after the break!

What's Inversion of Control?

Inversion of control is basically just a fancy term for plugins. For example, connect and express middlewares implement a sort of inversion of control:

connect.use(function invertedControl (req, res, next) {
  // Function now has control of our request and response

  // Code that may or may not manipulate the req and res objects.

  next();
});

The idea of "inversion of control" is usually contrasted with an older "procedural" paradigm. When writing code in a procedural style, you would have a central piece of code and change its behavior directly. Here's an example with python 2.7's BaseHTTPServer:

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

class RequestHandler(BaseHTTPRequestHandler):
  def do_GET(s):
    s.send_response(200)
    s.end_headers()
    s.wfile.write('Pong!')

server = HTTPServer(('', 8080), RequestHandler)


server.serve_forever();

This snippet runs a synchronous http server and uses classes for handlers. There isn't an equivalent here for node, since node's http server is asynchronous.

Even though it sounds complicated, it simply describes a family of design patterns where an object is manipulated directly by a called function.

In broadway, a basic example of inversion of control looks like this:

var app = new broadway.App;

// This will throw an error.
//app.say('Oh, what a world.');

// This plugin is often a separate module.
var sayPlugin = {
  attach: function attachSay () {
    this.say = console.log;
  }
}

app.use(sayPlugin);

// Will print 'Hi world!' as we expect.
app.say('Hi world!');

In this example, when our app .uses the sayPlugin, the attachSay function gains control of the app object (referred to in the scope of attachSay as this). Now all we need to do to give our broadway app a working .say is use the sayPlugin plugin!

This of course hardly scratches the surface of broadway's capabilities.

What are Dependency Injection and Feature Reflection?

Really, dependency injection is a particular flavor of these "inversion of control" patterns. Yes, I said the 'p-word'. But don't worry! All this means is that dependency injection is a 'thing'.

Dependency injection implies 3 things:

  • An injection container that uses plugins
  • plugins that modify the behavior of the container
  • A way to for some plugins to depend on other previously-loaded plugins

The Wikipedia article on the subject says as much, but focuses on the use of dependency injection in static languages like Java. For instance, the Spring framework for Java implements this approach. You already saw an example of the same style of "IoC container" in this article. The Spring framework's version, perhaps unsurprisingly, requires you to write as much XML as Java (perhaps more). I don't say this to dig on Java or Spring; I say this to point out how much more lightweight it can be in a dynamically typed language such as JavaScript.

In the previous broadway example, the app was our injection container and it used the plugin sayPlugin. But what about dependencies?

I'm fond of comparing building applications with broadway to assembling a megazord because of the way you can use multiple plugins that depend on each other to build a whole. For an example, suppose I'd like to give my app a head that lets it say things:

// head.js

exports.name = 'head';

exports.attach = function attachHead (opts) {

  var head = this.head = {};

  head.say = function speak () {
    var words = Array.prototype.slice.call(arguments).join(' ');

    console.log('The stranger looks at you and says, "' + words + '"');
  };

};

Now suppose I want to give my future bot a sweet hat:

// hat.js

exports.name = 'hat';

exports.attach = function attachHat (opts) {

  // This plugin *depends* on the "head" plugin being loaded.
  if (!this.head) {
    throw new Error('You can\'t wear a `hat` without a `head`!');
  }

  var stranger = this;

  stranger.hat = {};
  stranger.hat.tip = function hatTip () {

    stranger.head.say('Howdy stranger.');
    console.log('He tips his hat in that half-Tom-Selleck, half-Frank-Sinatra manner.');
  };

};

In this example, if the app has a head, it can wear a hat and gains the ability to .tip its .hat. By checking for this.head, our hat plugin requires that we've already .used the head plugin.

Here's what this looks like together:

// index.js

var broadway = require('broadway');

var hat = require('./hat'),
    head = require('./head');

var stranger = new broadway.App;

// Attach a head!
stranger.use(head);

// Now you can wear a hat!
stranger.use(hat);

// Tip dat hat
stranger.hat.tip();

Once we give our stranger a head, it can wear a hat and do a pretty good cowboy impression:

% node index.js 
The stranger looks at you and says, "Howdy stranger."
He tips his hat in that half-Tom-Selleck, half-Frank-Sinatra manner.

What About Application Extensibility?

We at Nodejitsu are excited about broadway because it allows for something we're calling "application extensibility". What does that mean?

Suppose that you would like to make a hooks-based plugin system for your broadway app, where the main module looks like so:

// index.html

var broadway = require('broadway'),
    hooks = require('./hooks'),
    app = new broadway.App,
    fooHook = {};

// Example of a hook we may want to attach,
// depending on an event hook interface
fooHook.attach = function attachFooHook () {
  this.hooks.foo = function () {
    console.log('You just called `this.hooks.foo`!');
  };
}

app.use(hooks);
app.use(fooHook);

// Initialize your app!
app.init(function (err) {
  if (err) {
    throw err;
  }

  // When we emit 'app::foo', we want `app.hooks.foo` to get called.
  app.emit('app::foo');
});

What we want is a nice way to attach 'hooks' that get called when app emits app::* events. Remember, broadway.App inherits from EventEmitter2.

The output of this example will look like this:

% node index.js
You just emitted app::foo!
You just called `this.hooks.foo`!

As it turns out, writing this plugin is actually relatively straightforward:

// hooks.js

exports.name = 'event-hooks';

exports.attach = function (opts) {
  var self = this;

  this.hooks = {};
}

exports.init = function (done) {
  var self = this;

  self.on('app::*', function () {
    console.log('You just emitted `app::foo`!');

    var plugin = self.hooks[this.event.split(self.delimiter)[1]];

    if (plugin && typeof plugin === 'function') {
      plugin.call(self, arguments);
    };
  });

  done();
}

First, in the plugin's attach method, we create the app.hook namespace for attaching new hooks. Then, later, the init method adds the relevant event listeners.

Cool! But even cooler: You can reuse this "hook" plugin for building apps over and over again, and then write further plugins that depend on the hook plugin.

A Real-World Example of Application Extensibility

Just last week, we released a new flatiron plugin called flatiron-cli-config. Here's a version of the example on the project's front page, to illustrate its use with flatiron, whose app is an extended version of the base broadway container:

// index.js

var flatiron = require('flatiron'),
    app = flatiron.app;

app.name = 'app.js';

// The flatiron app has loaded a basic `config` plugin.
app.config.file({ file: 'test-config.json' });

// Use flatiron's `cli` plugin
app.use(flatiron.plugins.cli, {
  usage: 'A simple CLI app using flatiron-cli-config'
});

// Use `flatiron-cli-config`, which depends on then `config` and `cli` plugins.
app.use(require('flatiron-cli-config'));

// Note that 'app.start' automatically initializes your broadway app.
app.start();

Based on the previous broadway examples, this should be straightforward enough. But, what the heck did the flatiron-cli-config plugin do? Basically, it gives you a complete CLI-based configuration API---the same as the one used by jitsu. For example, this should work:

% node index.js config set foo bar

Just like we gave our stranger a head and a hat earlier, we just gave this application a complete CLI interface including configuration with an index of only 9 lines.

Conclusions

Here's the tl;dr on broadway:

  • It's a plugin system.
  • It lets plugins do almost anything to the host app.
  • Broadway plugins are like your app's gear and power-ups.

Broadway is quite possibly my favorite component of all of flatiron. I hope you find the time to give it a spin! I think you'll find it worthwhile.