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 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.
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 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:
detatch. In Broadway a plugin looks like this:
As you see, each plugin attaches his piece of features to the main Broadway App, in the case above the
To load a plugin, you should do something like this:
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 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.
A plugin in Architect looks like this:
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 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:
The module, or service (as called by its documentation), can be then registered in the container:
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
Factorysuffix 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.
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.
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.
A service definition for the invocation above might look like this:
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:
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.
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:
- 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
- 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.
- 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.
- 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.
- I wasn’t satisfied with the performances, so I started with some optimizations, and replaced Q with When.js for promises.
- 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.
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.
What is happening in the code above is the following:
- Scatter, because of the non empty
prototypewill instantiate the module using
- The module
express/appis passed as argument of the constructor.
- A property is injected into the instantiated module, in particular the property
registerRoutesrepresenting a service. When the service
svc|sequence!routes/registeris executed, it will resolve all the modules providing the service
registerunder the namespace
routes, and then will invoke all those resolved services one by one in sequence.
- Exposes the method
registerAllRoutesas a new service, and specifies that it should be executed after the same service is invoked over the module
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!
If you look at the way you initialize your Scatter container, you may get a tip on why:
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:
Ideally, if you design your modules properly, you should be able to enable/disable each plugin, by just adding/removing it from the
This is only the tip of the iceberg, to know more about Scatter, visit its github page.
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.