Message Systems in Programming: Part 4 of 7 – Publish Subscribe

Publish Subscribe

Callbacks help objects/classes talk to each other in a less coupled way. Events for many to listen and react. However, for objects that deal with data or business logic, you need a way for them to globally communicate through some sort of event or message bus. Various libraries have sprung up in languages that do not have this natively available, or if the native API doesn’t fit the developer(s) need.

Enter publish subscribe.

Now, pub sub is actually a pretty high level term that pretty much encompasses this entire article.

Callbacks and Promises have a subscribe method, and they publish a method when they execute a callback. However, we’re referring to the ability to publish to multiple subscribers.

Events have a subscribe method via addEventListener, and a publish method via dispatchEvent. However, at least in the case of JavaScript, we’re referring to the same abilities WITHOUT the need for anything graphical involved.

A pub sub system COULD be made from a Stream, but I wanted to cover the implementations I’ve seen so you get an understanding why they not callbacks, events, promises, nor streams.

Pub sub IS the ability to:

  • be 1 instance that can broadcast to many listeners
  • have no need for anything GUI related
  • just be a class/object instance

Pub Sub Examples

Backbone’s Events class is the first example most in the JavaScript community refer to. You can either create an instance and pass this instance around via Require.js as a global telephone. Some get lazy (or are in a hurry or don’t know any better) and shove on window, or forget they can just use the Backbone object. Either way, here’s a manually created global one below:

var EventBus = {};
_.extend(EventBus, Backbone.Events);
window.EventBus = EventBus;

Now, any object or class, anywhere in the world, GUI or data, can go:

EventBus.trigger("onSomeEvent", 1, 2, {firstName: "Action", lastName: "Jackson"});

… and anyone and their mom can listen for it, anywhere via:

EventBus.on("onSomeEvent", function(event)
{
console.log("Who's the MAN?!: " + event.firstName + " " + event.lastName);
});

Instead of caring about the context (or where and how) a message is sent, you just… get the message.

The same concept applies to Runtime in Corona SDK:

Runtime:dispatchEvent({name="boxDragged", x=event.x, y=event.y, phase=phase})

And the View/Controller class that will visually update the object. She listens

function controller:init(view)
self.view = view
Runtime:addEventListener("boxDragged", self)
self.originalX = self.view.x
self.originalY = self.view.y
end
function controller:boxDragged(event)
self.view.x = event.x + 20
self.view.y = event.y + 20
local phase = event.phase
if phase == "ended" or phase == "cancelled" then
self.view.x = self.originalX
self.view.y = self.originalY
end
end

Notice Backbone’s is a custom implementation whereas Corona’s is a native implementation, built into the platform.

Angular’s uses a hybrid approach of event using their Dependency Injection system where anyone can reference the top node to hear all events bubbled (emitted) up:

// listen for it
angular.module("com.company.project.inventory", 
[
"com.company.project.utils"
])
// shows and hides children UI panels
.controller("UIPanelsController", function($rootScope)
{
$scope.inventoryShowing = false;
$scope.onToggleInventory = function()
{
$scope.inventoryShowing != $scope.inventoryShowing;
// 
}; $rootScope.on("toggleInventory", $scope.onToggleInventory); }); // use it angular.module("com.company.project.gameControls", [ "com.company.project.utils" ]) .controller("GameControlsController", function($scope) { // angular template be all like // <button ng-click='onToggleInventory()'>Show Inventory</button> $scope.onToggleInventory = function() { $scope.$emit("toggleInventory"); }; });

You send whatever you want. You can send wherever you want; doesn’t matter if within a button click handler, a deeply nested list, or somewhere in the bowels of your ajax code parsing whack JSON from a middle tier.

This simplicity and flexibility made Underscore and others of its ilk a lot more popular than the verbose Event system. Easier to learn, easier to integrate anywhere, and easier to read. You can still use events for UI related concerns to keep the bubbling and cancelable functionality, but for everything else in Application (what your app does) and Business logic (what back-end your app talks to and how), you can simply use a pub sub library. This is why they’re popular amongst MVC frameworks.

While Underscore doesn’t cover all the cool pub sub libraries out there, it’s the most approachable and gives you enough to be uber effective in understanding one basic approach to pub sub.

Pub Sub Pros

At their core, any “thing” in pub sub can publish a message. Any “thing” can subscribe to that message. A message can be whatever you want. They’re flexible, easy to grasp, and reasonably easy to debug if their dispatch function isn’t overly verbose.

Callbacks only support 1, but pub sub supports many. Pub sub have a more obvious API to subscribe and unsubscribe to messages and to particular messages than callbacks. They don’t expose nested UI parameters in their function signatures like Events do for addEventListener, nor do they care about where they’re dispatched from; if someone is listening for their message, they’ll get it. There is no native functionality to worry about canceling, so that too isn’t thrown in the API of messages where only 1% would actually have their preventDefault called.

Pub Sub Cons

What pub sub lacks that events has, this makes it basically assumed you’ll never use it for UI. Instead, you’ll use it in some form of MVC; whether intentional or not, to communicate UI concerns to higher level classes that DO things when someone clicks a UI element or some class needs to broadcast it has new data if any of those UI controls want to show it.

If for whatever reason you want to stop others from getting events, you can’t unless for some weird reason the API supports removing all currently subscribed listeners from the stack of a subscriber’s current callback execution (which isn’t elementary to support, mind you; many an experienced programmer has created messaging bugs forgetting this common use case). Most Stream API’s have this functionality, either by pausing the broadcaster from the listener at a bare minimum.

You can also get into race conditions where a subscriber subscribes too late and misses one or many messages. This leads to a lot of weird “orchestration” code to ensure all views and controllers are registered for services start loading data that the UI wouldn’t be ready to “handle”. There is no “did I miss any?” functionality unless the API has clever ways to do so. Promises and Stream fix this problem.

More importantly, though, you start to get into “message hell” as your application grows. Unlike callbacks which can be solved by flattened Promises, pub sub messaging systems are a global message hell. Everyone sharing the same telephone, like an 80’s sitcom. They can be way worse when under a deadline with lots of code. You’re constantly following the path of a message through various unrelated classes just to update a piece of data or scope. What set out to be a nice way to enforce encapsulation ends up being an unassuming tool that creates confusion and an unwieldy code base to debug. The key is to isolate concerns and treat pub sub as a “global” or “Singleton”. Once it’s viewed in that light, it makes it easier to only use when necessary.

Pub sub aren’t in those ECMA specs, nor natively implemented as new features in the browsers, nor as API’s in the new languages because of the issues mentioned above.

Everyone uses Pub Sub anyway because it’s extremely useful whether in games or applications.

<< Part 3 – Events | Part 5 – Promise and Deferred >>