Trevor shows how to make Node applications as cleanly organized as those in any other runtime environment.
Unit testing is a pain.
Well-tested code is great. That’s what I tell everyone. Who doesn’t? But more often than not, I just don’t wanna do it on my own projects. That’s partly because most functionality is hard to test in isolation. Unless you’re writing a calculator, your application contains a lot of units that each depend on several other units: Your game’s monster unit invokes sound and video components when struck by a bullet; your bug tracker’s issue unit summons email and RSS when a reply is added; and just about every file in a typical Node.js web app could well start with:
And so on. Node’s modularity is both a blessing and a curse. While frameworks like Rails make just about every piece of code accessible from everywhere else, Node forces you to make your dependencies explicit. That’s great when you have only a handful of moving parts, but when you have hundreds, it’s a nightmare. And how can anything be tested in isolation when so many dependencies are required?
Fortunately, there’s a way to simplify your application’s structure without simply making everything global: instead of having units communicate through function calls, have them emit events.
Emissions As Transmissions
Node.js is said to be event-driven because it has no threads. Instead, every time a script has finished running, Node checks if any events have been queued (via a method like setTimeout or readFile), and then runs one of the queued functions. The result is often more efficient and less bug-prone code than in other languages where several pieces of code are run “simultaneously” via threading.
Or you can make your own EventEmitter subclasses:
Or you can “mix in” EventEmitter’s prototype to add its functionality to an existing object:
But what makes the events even more powerful than ordinary function calls is that you can “stack” multiple listeners on the same event. The listeners are triggered in the same order they were attached in:
So, how can event-driven code save us from dependency hell? Let’s go back to that monster example from the start of the article. We want it to do a bunch of things when it gets hit. A straightforward approach would be to write code like this:
But look at all the dependencies we have here: audio, video, player, world... That leads to two problems: one is that we have to get references to those objects from every single Monster-like module, leading to lots of code duplication. The other is, how do we test Monster::hit? Surely we don’t want a bunch of monster noises to erupt every time we run our game’s suite of unit tests!
What if, instead, we use an event to separate our internal logic code from everything else?
Now all we have to do is listen for that event somewhere. Let’s say that the world object is responsible for spawning monsters, and when it does, it emits a new_monster event with the new Monster as an argument. Then in the audio object, we can add:
The video code would look much the same. And the player code would look like:
In many ways, this way of organizing code is more logical. It allows the Monster class to stay more “pure.” To unit test it, all we have to do is create it without emitting the usual new_monster event. And check out how much nicer our dependency graph has become!
Imagine No Dependencies
A lot of Node developers will tell you that attaching things to global rather than exports is a no-no. And if you’re packaging your code as a library, or trying to make your code reusable, then they’re right. But if you’re developing a standalone application, then go ahead and let yourself declare a few globals. In particular, if you’re using the pattern described in the last section, try adding this to your main application file:
Not only is this one line worth a thousand world = require 'world's, but it also makes the world dependency soft and easy to replace. At the top of your application’s tests, you could put this:
Now none of the listeners attached to world will ever actually be fired, and you can easily run assertions on how many listeners are attached for a given event and how many times that event has occurred.
Alternatively, you can take the lighter tack of simply adding additional listeners for testing and selectively using EventEmitter::removeListener to halt those persistent monster screeches during unit testing. You might take advantage of the once method for your assertions, which acts just like on except one-time-only:
Conclusion: Making the Most of Modules
Despite Node’s reputation for complexity, I’d argue that it’s possible to make Node applications as cleanly organized as those in any other runtime environment. The event paradigm provides an elegant way of connecting objects, providing maximum flexibility and minimum boilerplate. It’s test-friendly, and encourages application code to be split up more by categories of functionality and less by imperative order. There’s nothing else quite like it.