Not Enough of a Good Thing
Pub/sub in a web client isn’t an obscure mystical pattern – you’ve been using it whether you realize it or not. At its most basic level, the DOM events are published to subscribers (handlers), which can then respond to the event. The browser, at its core, is exposing a very powerful pattern to us. Something that, if taken advantage of, can assist you in structuring your web apps to avoid the easy traps of spaghetti-code and brittle architecture. In order to understand the benefits of extending the browser’s “evented” nature into our own application code, I want to spend some time with a simple example that highlights the kind of (painful) approach I – and many fellow developers – find typical of many web apps that I’ve encountered.
Most of us have seen something like this (click here to open this jsFiddle in a new window):
In this example, we’re taking advantage of the syntactic sugar jQuery provides to wire an event handler up to an event. In one operation, we’ve ‘subscribed’ to the click event with a handler that will clone one element and append it to another element (keeping tracking of selected values). This approach reeks of code smell. First, the “#products” and “#cart-list” elements are now tightly coupled. Second, there’s no backing model for the state of the view. “What” you would call such a model will vary based on the architectural approach you use – “model”, “view model”, etc. Regardless of the debates that would arise around such approaches, you cannot ignore the reality that the “model” is so tightly coupled to the view in this example, that testing apart from the view is impossible. Changes to the model will require view changes and vice versa. But wait, it gets even better.
Let’s pretend the customer loved our solution so much, that they wanted to add a summary block at the bottom of the page that kept track of how many items were selected. In addition, they don’t want the products being added multiple times to the cart, intead they want to keep track of qty per product on one line (click here to open this jsFiddle in a new window):
Ah, the classic smell of spaghetti! Now we’ve made matters worse by including more code in the “click” handler that further exposes the cart’s “internals” to the product list’s click function. Our “#products” element is now tightly bound to the “#qty-selected” element, as well as the “#reset” button. At this point you might ask, “But these elements are all part of the same page, what’s wrong with coupling them?” In the simplest of apps, maybe nothing. However, I’ve never run into a situation where a web app I’m responsible for has stayed so perfectly simple, that any lack of architectural vision hasn’t come back to haunt.
Let’s back up and talk about the goals of such a page:
- Let the user view and select products
- Provide a “cart” for the user to store selected items
- Provide additional components (like a cart summary, related items, “other items purchased with these items”, etc.)
- Submit the order
It’s reasonable to suggest that above bullets act as a good dividing line between what we should be thinking of as “separate modules” (with limited-or-no knowledge outside themselves) interacting on the same page. We may want to not display some of the additional components based on the client accessing the site (or display a different set of components altogether). If we’ve coupled them together by making them directly aware of each other through event callbacks, etc., then we have the painful task of de-coupling before we can support that functionality. We might want to present different types of product lists (a text list, a thumbnail view, etc.). Again, if we’ve tightly coupled the first product list type to the other components, odds are we will do the same to any future product list types. In addition, if we’re not using a backing model to track what products are available, and which ones the user has selected, then with each new product list type, we’re re-inventing the wheel (i.e.- writing new code) as we parse the view for state.
What if, instead, we chose to create a products module, a shopping cart module, and a summary module, where each one would subscribe to events occuring in the application, as well as generate events for other subscribers? For example, the products module could generate an event – or a message – when the user selects one by clicking on it. The shopping cart module could listen for any messages that say “add this item to the cart”. The summary module could also listen for messages related to items being added to the cart.
The good news? We can take this approach, and it’s not terribly difficult to accomplish it. In the next post, I’m going to walk through how we can refactor this example to use client-side messaging to decouple the components using postal.js. Here were the goals I had in mind in writing postal:
- Allow subscribers to listen to specific “topics” or wildcard versions of topics.
- Allow subscription publish events to be throttled/limited based on model state (via predicates), throttling/debouncing, and deferred (until browser event loop is clear).
- Support “Channels” to logically group subscriptions by purpose (like a namespace), and to improve performance (fewer subscriptions to traverse when publishing)
I hope you’ll stick with me not only for the next post – where we’ll turn this example into something much more respectable – but as I follow up with additional posts that cover more of the advanced capabilities postal.js provides.