Skip to Main Content

Dependency Injection and Framing for Node.js

Michael Pardue, Accusoft Senior Software Engineer


When developing as a team, a persistent major concern is how to structure an application so that all team members can add features, maintain code, and fix problems without stepping on each other’s toes. We wanted to solve this for Node.js by creating a way to divide our applications into discrete and shareable components. So we built a new node module, Framing. Framing lets us not only divide our application into components, but also declaratively structure and initialize our application.

How did we get there?

Well, if the application is divided into components, there needs to be some way to wire them together. We could have each component construct the dependency components it needs, but this sort of tight coupling makes maintaining and changing the application difficult. As the number of places that must be modified increases, the probability of errors also increases. The size of the team only acts as a multiplier, causing the problems to cascade. Separating the concerns of object construction and the application’s business logic decouples the areas of the application and reduces what must be modified to add and maintain features.

Ideally, the business logic should be totally separated from the object construction. If an application requires an object, then the fully instantiated object should be given to the application. This is often referred to as Inversion of Control (IOC). IOC generally allows the transformation of some task from an imperative paradigm to a declarative paradigm—the developer only describes what is needed and the framework takes care of how the requirements are satisfied.

A typical tool for IOC is dependency injection (DI). Applications will request from DI frameworks a particular kind of object and the DI framework will construct and pass dependency objects into dependent objects for the application. As dependencies have their own dependencies, this generally translates to the generation of a dependency tree that the framework will traverse until all dependencies are satisfied.

DI Frameworks usually employ the factory pattern. The factory constructs the objects and returns the instance when the application requests the object by type or name.

Using a Factory
Factory.createInstance();
Factory.createInstance('name');

But dependency injection does not go far enough. The application must still request the objects. The application still needs to know what objects to initially request and it still needs to apply the business logic to glue the objects together. DI improves the separation of concerns but it can be taken a step further. If the dependencies can be declared, then the root(s) of the application can be determined by the calculation of the dependency tree.

We created Framing, an open source library for Node.js, to do this very thing. Framing simplifies the organization of the application into components by simplifying both the creation and the consumption of components. Framing components are just node modules with extra metadata and initialization code. When one component depends on another component, the dependency is declared as part of the definition of the consuming component rather than requested from a DI library. The collection of Framing components added to an application will automatically be initialized and wired together at application startup.

Framing components are declared by adding metadata to a node module’s package.json file. The framing property contains the Framing component name (not necessarily the same as the node module’s name) and what other Framing components it depends upon.

A Basic Framing Component
package.json
{
  "name": "my-service",
  "main": "index.js",
  "framing": {
    "name": "service",
    "imports": [ "logger" ]
  }
}

index.js
module.exports.initialize = (imports) => {
  imports.logger.debug('Initialized...');
  return new Promise((resolve) => resolve({
    say: name => `Hello, ${name}`
  }));
};

Adding Framing to an application is easy. In the application project, install the framing node module, require framing in the main file, set the base directory (so Framing can find folders it needs, like node_modules), and call run.

Starting a Framing Application
index.js
require('framing')
  .setBaseDir(__dirname))
  .run();

When Framing starts, it first generates a dependency tree of all the components, establishing the relationships between them. It discovers components by iterating through the node_modules folders and through any other folders defined by the application. Armed with this information, Framing makes decisions about which components are safe to initialize first and which components should initialize in parallel.

By initializing components asynchronously and in parallel, you gain a lot of flexibility and power. For example, consider a config component that must pull its config data from an external resource. Any component that relies on the config component will safely block until the config component is initialized. The developer does not have to write the code to glue these components together because Framing will do it automatically. Furthermore, if multiple components need to initialize by hitting external resources, they can all safely and automatically initialize at the same time, allowing your application to start that much faster.

Additionally, because Framing automatically executes the initialization code of all Framing components, the main entry point of your application can even be defined as a Framing component. If you do this, adding new features or startup code is as simple as installing another Framing component with npm install.

Framing makes building and maintaining complex applications easier by allowing you to break your application into separate node modules where each module handles its own initialization. With this sort of approach, distributing the work across engineers and even teams becomes much more manageable.

Framing is available in npm with source available in github. Give it a try and let us know what you think.


Michael Pardue, Senior Software Engineer at Accusoft and Chief T-Rex Artist. He is highly knowledgeable in a number of technologies including Node.js, microservice architecture, cloud computing, continuous integration, and unit testing. He holds a Bachelor of Science in Computer Science from University of South Florida. He ends his days playing musical instruments and video games.

Want more resources and insights?

Subscribe Now