Sunday 20 October 2013

Loose Coupling Between Node.js modules - Communicating Between Modules

TL;DR

Here is how you can use the EventEmitter class in Node as a shared message bus to allow for loose coupling between modules.

Shared Event Emitter to be used as message bus - sharedEventEmitter.js

var events = require('events');
var eventEmitter = new events.EventEmitter();
module.exports = eventEmitter; 
//The eventEmitter will be used as a singleton

Module to publish events - publisher.js

var sharedEvents = require('../sharedEventEmitter.js'); 
var worker = function() {
 var doWork = function() {
  var result = ...
  sharedEvents.emit('work done', result);
 };
};
module.exports = worker; 

Module to listen for events - listener.js

var sharedEvents = require('../sharedEventEmitter.js'); 
sharedEvents.on('work done', result) {
 //do stuff with result
}

Module to tie everything together

var listener = require('../listener.js'); 
var publisher = require('../publisher.js'); 
publisher.doWork();

Why would you want to do this?

If you are working on a big app or communicating between Node processes this solution won't scale very well. If you need more flexibility look into something like AMQP or the available Node.js modules for message queuing.

Having said that, for a lot of projects that is overkill. Maybe you're working on a small app, but you still want to write loosely coupled easy to test code. I'll use a fairly contrived example to show a situation where this could be used.

Consider a basic online store and imagine that two related modules exist.

One module parses a data feed of product data from a supplier. This module updates the database with stock levels etc.

Another module alerts a user when a product they have ordered becomes available.

The module that parses the data should not have knowledge of user accounts. This module has one purpose, parsing the feed. And the module that handles alerting users should not interact with the parsing code.

When I say "should not" I'm referring to best practices, obviously you can, but you'll end up with code that is hard to test and maintain. For example if we were to call a method from the parsing code, something like updateUsersNowThatProductsInStock we are tying the functionality of the parsing code to the user alerting code. If we then want to use that parsing code somewhere else we'll more than likely have to delete the user alerting section of code. Also testing the parsing code would require mocking or stubbing out that method - it's just more work.

So how do we know when to send alerts? We don't want to have to poll the database as that uses unnecessary resources - which is definitely a concern if you pay for computing resources on something like heroku.

One way to achieve this is to use the EventEmitter class in Node.js as a message bus.

First create a module that exports an instance of an EventEmitter. We will use this as a singleton. Each module will 'require' the shared EventEmitter.

It Node.js the idiomatic way to implement an EventEmitter is for the object emitting events to subclass the EventEmitter. In this case we don't want to do that as the module listening for those events would then have a direct dependency on the Emitter, which is exactly what we're trying to avoid.

We are essentially implementing a very basic version of the Publish Subscribe paradigm, but just between modules.

Once your modules are using the shared event emitter you can then interact with them via a third module.

You should bear in mind that the module publishing the events does not emit any events until a function is explicitly called. This is important because you need to ensure that your event listener is attached before you fire your event. In the example above the event listener is attached within the root module scope, so just 'requiring' this module will set up the listener.

If you wanted to do some work whenever your publisher module was imported you would need to make sure that you 'required' the modules in a specific order, listener then publisher.

You may question how this is any different to just "requiring" the two modules within the App.js/Server.js file, why the third module? In this case it is solely to be explicit about the interaction between the two modules, which would likely get obscured among the many other modules you'd be 'requiring' in the main app file. It basically allows anyone looking at the code to easily see that the modules interact in some way.

Each module carries out a completely separate task, but at a higher level of abstraction they could be seen as one logical unit. Creating a third module makes the code easier to understand and doesn't affect the testability as each module can still be tested independently.

NOTE This approach will not work easily if the events need to go in both directions as you'd need to be very careful about the order of declaring emitters and listeners, which is not ideal between two files. If you have that requirement look into message queuing.

No comments:

Post a Comment