a dependency injector for node.js
Forge is a dependency injection framework for Node.js. It was originally built as an experiment, but is now in use in production at Adzerk, where the software it powers handles billions of requests each month.
Table of Contents
- Foreward, and forewarning
- Getting started
- So how's it work?
- Multiple bindings for a single component
- Conditional bindings and resolution hints
- Dependency hints
- Explicit arguments
- Unbinding and Rebinding
Having now written two sizable projects in Node, I've come to believe that an object-oriented approach backed by a dependency injection system is the most effective way to develop complex systems.
This is something I built to scratch my own itch, and have released it in the hopes that others will find it useful as well.
You can install Forge from npm:
$ npm install forge-di
The API of Forge is inspired by some of my previous work on Ninject, a dependency injection framework for .NET. I wouldn't go so far as to call Forge "Ninject for Node," but it's not an entirely unreasonable moniker.
Forge allows you to define loosely-coupled components in a declarative way, and then wires them together for you as necessary. Here's a quick example of typical usage:
Forge = require 'forge-di'# A class with a single dependency:# A class with no dependencies: -># Declare your bindingsforge =forgebind'foo'totypeFooforgebind'bar'totypeBar# Resolve an instance, and Forge resolves the dependency graph for youobj = forgeget'foo'assertobj instanceof Fooassertobjbar instanceof Bar
Forge works by examining the names of the constructor parameters on your types. In the example above,
the constructor of the class
Foo had a parameter called
bar. Forge sees this as a dependency,
resolves the component named
bar and passes the resolved instance to the constructor of
Instead of constructors, you can also bind to functions, which Forge will call to resolve the dependency. (This is helpful if you need to do something more manual to resolve the dependency.) If the function has any parameters, Forge will attempt to resolve them when calling the function.
Forge = require 'forge-di':: -># A simple factory function with a single dependency= bar# Declare a binding to the function, and another to the dependency it requiresforge =forgebind'foo'tofunctioncreateFooforgebind'bar'totypeBar# Forge will call the functionfoo = forgeget'foo'assertfoo instanceof Fooassertfoobar instanceof Bar
You can also register multiple bindings for a single component name. For example, you might want to create a class that can operate as a facade over an arbitrary number of plugins:
Forge = require 'forge-di': ->: ->:# Register multiple bindings for the "plugins" componentforge =forgebind'plugins'totypePluginOneforgebind'plugins'totypePluginTwoforgebind'facade'totypeFacade# Forge passes an array of instances to the Facade constructorfacade = forgeget'facade'asserttypeof facadeplugins == 'array'assertfacadepluginslength == 2assertfacadeplugins0instanceof PluginOneassertfacadeplugins1instanceof PluginTwo
To support this behavior,
Forge.get() will return a single instance when only one matching
binding is available, and an array of instances when multiple bindings are available.
To be certain that you only resolve a single instance, you can call
This function will throw an exception if more than one matching binding would be resolved.
If, instead, you want to resolve all bindings for a given component, you can call
This will ignore the conditions specified on the bindings, and resolve all of them. This
function will always return an array of instances, even if only a single instance was resolved.
Since you can register multiple bindings for a single component, you might not always want
to resolve all of them for a given scenario. To allow this, Forge supports conditional bindings,
which are bindings with a predicate attached. These predicates examine resolution hints
that you pass to
Forge.get(). For example:
Forge = require 'forge-di': ->: -># Register multiple bindings to the same component, with predicatesforge =# Here's the short form, which just does an equality (==) check against the hint:forgebind'foo'totypeRedFoowhen'red'# And here's the long form, which allows you to pass in a predicate function:forgebind'foo'totypeBlueFoowhen hint == 'blue'# Forge passes the resolution hint to the bindings' predicates to determine which to resolvefoo1 = forgeget'foo''red'foo2 = forgeget'foo''blue'assertfoo1 instanceof RedFooassertfoo2 instanceof BlueFoo
Hints don't need to be scalars; they can be anything you want, as long as the predicates associated with your conditional bindings understand how to evaluate them.
If multiple bindings match,
Forge.get() will return an array of results.
(See Multiple Bindings for a Single Component above.)
If you don't want to rely on the convention of naming your constructor arguments the same as your
components, you can add dependency hints to your types instead. The syntax is reminiscent
:"dependency1 -> foo""dependency2 -> bar"
When Forge creates an instance of
TypeWithHints, instead of trying to resolve components named
dependency2, instead it will read the hints, and resolve components named
If you have conditional bindings registered, you can use dependency hints to specify that you'd like to resolve all of the available components, regardless of whether conditions have been set.
Forge = require 'forge-di': ->: ->:"plugins -> all plugin"# Register multiple conditional bindings for the "plugin" componentforge =forgebind'plugin'totypeRedPluginwhen'red'forgebind'plugin'totypeBluePluginwhen'blue'forgebind'facade'totypeFacade# Forge disregards the conditions and passes an array of instances to the Facade constructorfacade = forgeget'facade'asserttypeof facadeplugins == 'array'assertfacadepluginslength == 2assertfacadeplugins0instanceof RedPluginassertfacadeplugins1instanceof BluePlugin
Finally, if you have conditional bindings registered, and you'd only like to resolve a single one, you can specify an additional hint you'd like to use to resolve the dependency in the hint itself. (Yo dawg, I heard you like hints...)
Forge = require 'forge-di': ->: ->:"plugin -> plugin: blue"# Register multiple conditional bindings for the "plugin" componentforge =forgebind'plugin'totypeRedPluginwhen'red'forgebind'plugin'totypeBluePluginwhen'blue'forgebind'facade'totypeFacade# Forge uses the additional hint to determine which conditional binding to resolvefacade = forgeget'facade'assertfacadeplugin instanceof BluePlugin
By default, once a binding has been resolved, the result will be cached and re-used for subsequent requests. This is called a singleton lifecycle, after the pattern of the same name.
You can define a lifecycle for a binding using the following syntax:
Forge = require 'forge-di':: ->forge =# Since singleton is the default lifecycle, you can also omit .as.singleton()forgebind'foo'totypeFooforgebind'bar'totypeBarassingleton# The instance of Bar is created on the first request, and then is re-used when creating Foobar = forgeget'bar'foo = forgeget'foo'assertfoobar === bar
Forge also supports a transient lifecycle, which means that a new result will be resolved on each request for a given component. Here's the same example as above, using the transient lifecycle instead:
Forge = require 'forge-di':: ->forge =forgebind'foo'totypeFooforgebind'bar'totypeBarastransient# There were two instances of Bar createdbar = forgeget'bar'foo = forgeget'foo'assertfoobar !== bar
Rather than allowing Forge to figure everything out for you, sometimes you may want to take manual control over part of the dependency resolution process. To allow this, Forge lets you supply explicit arguments to your bindings, overriding dependencies by name.
Here's an example:
Forge = require 'forge-di'# A class with a single dependency:# Manually create an instance of the dependencymanuallyCreatedBar =# Tell the binding when "bar" is requested, just return the instance we just manually created.forge =forgebind'foo'totypeFoowithbar: manuallyCreatedBar# Forge will pass the explicit argument to the constructor of Foofoo = forgeget'foo'assertfoo instanceof Fooassertfoobar === manuallyCreatedBar
You can also use this to specify arguments to bound functions. This is helpful to create factory functions if you need them:
Forge = require 'forge-di'# A factory function with a single argument= bar# Manually create an instance of the dependencymanuallyCreatedBar =# Explicitly set the value for the parameter named "bar"forge =forgebind'foo'tofunctioncreateFoowithbar: manuallyCreatedBar# Forge will pass the explicit argument to the function.foo = forgegetassertfoo instanceof Fooassertfoobar === manuallyCreatedBar
Note: if (for some reason) you specify a dependency hint on one of the arguments to a constructor or function, the explicit argument name must match the hinted name, not the actual name of the argument.
Sometimes, you might want your components to do on-demand resolution of dependencies, rather than having them injected immediately when the component is created. To do this, you can create a factory type, or you can just ask Forge to pass a reference of itself to the created component.
Here's an example of a dependency on the Forge itself:
Forge = require 'forge-di':forge =forgebind'dependent'totypeDependsOnForgeobj = forgeget'dependent'assertobj instanceof DependsOnForgeassertobjforge === forge
Use this sparingly! You should always favor constructor injection to service location.
Forge supports altering bindings after they have been defined via two methods:
unbind(), which removes any existing binding and does not replace it.
rebind(), which removes any existing bindings and begins a new binding definition.
Here's an example of unbinding:
Forge = require 'forge-di': ->forge =forgebind'a'totypeFoo# Returns the number of bindings that were removed.count = forgeunbind'a'assertcount == 1# This will throw a ResolutionError.forgeget'a'
And here's an example of rebinding:
Forge = require 'forge-di': ->: ->forge =forgebind'a'totypeFooa = forgegetFooasserta instanceof Fooforgerebind'a'totypeBara = forgeget'a'asserta instanceof Bar
Be careful with unbinding and rebinding! Treating your container as mutable can make it very easy to get into a confusing situation where the bindings between your components are unclear. This functionality is primarily provided to make setting up integration tests easier — avoid using it at runtime.
Copyright © 2014 Nate Kohari. Released under the Apache 2.0 license. See the file LICENSE for details.
Image of forge — Foyer de la forge Mustad by Frédéric BISSON (CC-BY)