How to Write Highly Scalable and Maintainable JavaScript: Coupling

Originally published at innoarchitech.com here on October 14, 2014.

Articles in This Series

  1. The “Wild West” Syndrome
  2. Namespacing
  3. Modules
  4. Coupling

Introduction

In the previous chapter of this series, we discussed modular JavaScript. We looked at native patterns and some of the prominent modular specifications and formats such as AMD, CommonJS, etc.

In this final chapter, we’ll examine ways to reduce coupling between JavaScript modules.

Coupling

Coupling between modules occurs when one module directly references another module. In other words, one module “knows” about another module.

For this chapter, assume that we are building a web application that allows people to place food delivery orders. Each time the user places an order, the app creates the order and sends a confirmation to the user that includes the estimated time of delivery. The user can check the status, or cancel the order at any time.

One modular approach to implement this is to create a module that handles orders and another that handles deliveries. The ordering module may have functions to create, read, update, and delete orders (i.e., CRUD), while the deliveries module may have functions to estimate delivery time, begin delivery, complete delivery, and so on.

Let’s look at an example implementation. Note that the code in this post is written primarily for clarity.

Example: Create Order

// Order module definition
var orderModule = (function() {
var module = {},
deliveries = myApp.deliveryModule;

module.createOrder = function(orderData) {
var orderResult;

orderResult = // Code to actually create the order
orderResult.estimatedDeliveryTime = deliveries.getDeliveryTime(orderData);

return orderResult;
};

return module;
})();

The order and delivery modules shown are tightly coupled. For the order module to get the estimated delivery time, it has to “know” about the delivery module, and call the appropriate module’s API.

There are many reasons to avoid tightly coupling your modules. We discussed some of these in the first chapter of this series. Recall that one of the goals when creating highly scalable and maintainable JavaScript applications is that any module can be easily swapped out at any time for a different module. Reusability is also a major reason to minimize coupling. Ideally, we would like to maximize code reuse and the ability to test modules independently.

Another goal was that there should not be a single point of failure anywhere in the application. Suppose that something went wrong in our call to get the estimated delivery time, this may break the entire application, or at least the successful completion of the ordering process. We should prefer that the order is still placed and the application continues to operate, even if we are temporarily unable to provide a delivery time estimate to the customer.

Now let’s examine some ways to reduce coupling between modules.

Patterns to Reduce Coupling

Tightly coupled modules have a variety of disadvantages as noted. Luckily there are ways we can reduce coupling, which includes many patterns used to achieve loose coupling between modules. These patterns are often a variation of the so-called observer pattern. One such variation is referred to as the Pub/Sub or Publish/Subscribe pattern.

In some cases the observer registers itself with the event emitter directly in order to be notified whenever a certain event occurs. The downside to this approach is that an observer “knows” about the event emitter object and what observables or events to observe through the registration process.

We can do better. There are many versions of the Pub/Sub pattern that involve a mediator object, which helps to further minimize coupling between modules. A mediator object is an object that isolates the publisher from the subscriber.

Addy Osmani made an excellent analogy when he related the mediator to a flight control tower in airplane communications. Airplanes never communicate directly with each other. Airplanes instead only provide information to, and receive information from the tower, and therefore do not “know” about one another without information from the tower.

There are a variety of libraries available to implement Pub/Sub-type patterns. We will use PubSubJS from Morgan Roderick, which is a topic-based JavaScript Pub/Sub library, and can be found here. Topic-based simply means that there are topics that a module can either subscribe to, publish to, or both. A module is also able to unsubscribe from a topic if needed.

We’ll now revisit our order example from before, but instead implement it using PubSubJS. Here is the code:

Example: Estimated delivery time using Pub/Sub pattern

document.addEventListener("DOMContentLoaded", function(event) {
var orderModule = (function() {
var orders = {},
EST_DELIVERY = 'current estimated delivery time',
estimatedDeliveryTime;

PubSub.subscribe(EST_DELIVERY, function(msg, data) {
console.log(msg);
estimatedDeliveryTime = data;
});

return orders;
})();

var deliveryModule = (function() {
var deliveries = {},
EST_DELIVERY = 'current estimated delivery time';

deliveries.getEstimatedDeliveryTime = function() {
var estimatedDeliveryTime = 1; // Hard-coded to 1 hour, but likely an API call.

PubSub.publish(EST_DELIVERY, estimatedDeliveryTime);
};

return deliveries;
})();

deliveryModule.getEstimatedDeliveryTime();
});

For simplicity, all required code and both module definitions are included in the same document ready event handler. Here we define two modules; one for orders and the other for deliveries respectively.

Notice that we’ve defined a topic with the EST_DELIVERY constant called ‘current estimated delivery time’. Any module can publish and/or subscribe to this topic without ‘knowing’ of each other’s existence. In this case we’ve directly called the getEstimatedDeliveryTime method of the delivery module.

By calling this method, the delivery module is used to retrieve the current estimated delivery wait time, which is likely based on the number of delivery orders currently in the queue, and then publishes the expected estimated delivery time to the EST_DELIVERY topic. At this point, any subscriber to this topic will be notified of the updated and latest delivery time estimate.

The order module subscribes to this topic. It will therefore always have the most up-to-date estimated delivery wait time to use when orders are placed, but without having to “know” about the delivery module and its methods, events, and so on. We can go a step further and implement a mechanism that regularly updates and publishes the estimated delivery wait time to this topic on regular intervals, or each time an order is placed and the queue is changed.

This is just one example of the many ways to use the Publish/Subscribe pattern to your advantage. There are however some potential downsides to this level of loose coupling, and I encourage you to read some of the resources noted in the references section for further discussion on the matter.

Summary

Throughout this series we have discussed ways to write highly scalable and maintainable JavaScript code, as well as covered some industry best practices and patterns used to achieve this goal.

We also discussed the “wild west” syndrome and why it’s so important to consider proper JavaScript code design, architecture, organization, and coupling. We considered patterns such as namespacing and modules, as well as the various ways to implement modules using plain JavaScript or by using a specification format such as AMD, CommonJS, and ECMAScript Harmony.

In this final chapter we discussed coupling between modules and ways to minimize it. Patterns such as the observer and/or a variation of Publish/Subscribe are excellent choices for reducing coupling. Loose coupling is very important for promoting code reuse, independent testability, interchangeability, and protection against a single point of failure.

When the above goals and techniques are considered and executed properly, your JavaScript code can most certainly be highly scalable, maintainable, usable, reusable, sustainable, extensible, and so on.

Cheers and happy coding!

About the Author: Alex Castrounis founded InnoArchiTech. Sign up for the InnoArchiTech newsletter and follow InnoArchiTech on Twitter at @innoarchitech for the latest content updates.