Articles in This Series
Coupling between modules occurs when one module directly references another module. In other words, one module “knows” about another module.
For this article, 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 article is written primarily for clarity.
Example: Create Order
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.
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.
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
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.
In this final article 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.
Cheers and happy coding!