One of the more valuable lessons that I’ve learned in the years I’ve been programming is that loose coupling will make your life better. A very good way to achieve loose coupling in your projects is by dividing functionality in self-sufficient modules that communicate merely by subscribing to and triggering events. To work with these events, you need one component that is relatively tightly coupled with all your modules. This little bit of coupling is definitely worth it though.
Before diving into specifics, let’s talk about just what I mean.
When the code base for a project gets sizeable, one thing that can make it a nightmare to work with is tight coupling. When modules are tightly coupled in a project, it means that changing one method in module A may require you to change the way certain things are implemented in module D, F and G. In order to make any modifications, you need to be well aware of how other modules rely on the module you wish to modify.
Since having a single module in your head can be taxing enough for the mind, considering basically the whole project when making a local change is awful; oversights are bound to occur.
To ensure you do not need to wrap your head around the entire code base at all time, you’ll want to make sure your modules are loosely coupled. You can safely change the modules’ internals without having to worry about how other modules have to interact with this module. You need to consciously engineer your modules to be entirely agnostic of the inner workings of others.
You’ll find that employing this method makes it much less of a daunting task to change some functionality around.
Bring on event-driven programming
Event-driven programming is a fairly simple pattern where component subscribe to events and trigger them. A variant of the pattern is the observer pattern; the difference however lies in where the event dispatching happens.
In an observer pattern, you can register observers (subscribers) to event a certain module emits. This way you can subscribe to, for example, an after-save event, which is emitted every time an object’s data is saved. The observer subscribing to this event will be triggered every time that happens, and a function can then be run.
In this case, we move the subscribing facilities away from the modules to a dedicated event dispatcher module. This provides one central location where modules sign up to be notified when a certain event occurs, as well as where modules go to broadcast an event they want to make the application aware of.
Doing this, all each module is concerned with if it comes to communicating with the rest of the application is 2 uniform things: Emitting events and responding to events emitted elsewhere. The modules do their thing, throw out some information whenever there is something that may concern the rest of the application, and they remain alert for external events pertaining to the module in question.
Responsibilities of the event dispatcher
The event dispatcher module that drives this whole pattern has only few responsibilities:
- Keeping track of event subscriptions
- Dispatching incoming events to all the subscribers of that event