Today, I’d like to talk about EventHub
, our in-browser publish/subscribe system. EventHub
does a very similar thing to OpenAjax Hub, but for slightly different reasons. Whereas OpenAjax Hub is designed to allow mash-ups of co-operating components from different sites, EventHub
is used to allow apps to be composed from a library of co-operating, loosely-coupled components.
OpenAjax Hub has security concerns that don’t affect our use case, but both systems facilitate mediated inter-component communication, and both systems only allow this communication to take place between components that pre-define the communication protocol(s) they will use.
An important facet of this type of communication is that events are just broadcast into the hub, with no knowledge of whether there are any other components actually listening or not. Additionally, components aren’t allowed to respond directly to events, except by broadcasting additional events that the original emitter might listen out for.
The OpenAjax Hub Way
Here’s some example code taken from OpenAjax Hub’s specification showing how communication works. First, here’s the broadcasting code:
var location_data = { lat:current_latitude, long:current_longitude };
OpenAjax.hub.publish("org.example.location.set",location);
and here’s the corresponding subscriber code:
function onDataCallback(topic, data) {
// Do something in response to the event and its data
}
OpenAjax.hub.subscribe("org.example.location.set",onDataCallback);
Although this simple example works fairly well, our experience is that things get messier as you scale up the number of events being sent. You either end up having to subscribe on lots of different topics, or a single topic is used where different parameters are sent in the data bundle depending on the actual message being communicated.
When this happens, the code on both the broadcasting and receiving end has a smell about it, and you realize that you’re essentially marshalling and unmarshalling data just so you can communicate. In fact, you end up with a subscriber that looks more like this:
function onDataCallback(topic, data) {
switch(data.message) {
case "locationUpdate":
locationUpdate(data.lat, data.long);
break;
case "locationProviderOffline":
locationProviderOffline();
break;
case "locationProviderOnline":
locationProviderOnline();
break;
}
}
The EventHub Way
For EventHub we chose to use interfaces as the way of representing the contract between parties rather than a protocol based on topics and data bundles. This makes it easier to create API documentation for, allows the IDE to provide content-assist, and removes the code smells too.
Javascript doesn’t provide a formal notion of interfaces (see Putting The Java back into Javascript), but we define our interfaces like this:
/**
@interface
/ LocationListener = function() {
};
/**
* Callback fired each time the user's location is updated.
*
* @param nLatitude {Number} The number of degrees north of the equator.
* @param nLongitude {Number} The number of degrees east of the Prime
* Meridian (Royal Observatory, Greenwich).
*/
LocationListener.prototype.onUpdateReceived = function(nLatitude, nLongitude) {
};
Here’s the broadcasting code:
var oFineGrainedLocationProxy = caplin.core.event.EventHub.GlobalEventHub.getProxy("LocationListener", "location.fine-grained");
oFineGrainedLocationProxy.onUpdateReceived(nCurrentLatitude, nCurrentLongitude);
and here’s a class that both implements the receiver interface, and adds itself as an event listener in its constructor:
LocationWidget = function() { caplin.core.event.EventHub.GlobalEventHub.subscribe("LocationListener", "location.*");
};
caplin.implement(LocationWidget, LocationListener);
LocationWidget.prototype.onUpdateReceived = function(nLatitude, nLongitude) {
// do something with the received location information
};
What just happened!?
Well, cool stuff actually!
Firstly, the broadcaster created a proxy object on the first line of code that happens to implement the LocationListener
interface. This proxy can then be used throughout the code to communicate with any subscribers that might be listening in. This is very similar to what happens with Remote Method Invocation in Java, where the application communicates with a proxy object responsible for marshalling the calls over the network.
Next, the listener objects implement the interface, which just means they define the methods defined by the interface. One added bonus in our environment is that the caplin.implement()
line will cause us to fail fast if an object doesn’t implement any of the mandatory methods, but also adds empty stubs for any optional methods that haven’t been implemented. This is really useful when our clients upgrade the SDK we provide, and some of the interfaces have grown in the interim.
Another difference to OpenAjax Hub is the use of channels, which could allow us to have separate fine-grained and coarse-grained location providers, where the coarse grained provider starts providing location information more quickly, but with less accuracy. Subscribers that are fussy about accuracy can subscribe to "location.fine-grained"
, whereas those that aren’t bothered can subscribe to "location.*"
and start receiving updates more quickly.
What’s our motivation for doing this?
Some time ago, we made the move to develop and test all our application components in isolation from each other. Whereas we used to deliver an SDK and a monolithic reference implementation that was really hard to re-use functionality from, we are now focused on delivering a number of starting-point apps where the functionality is provided using independent drop-in/drop-out components we call blades.
These blades have to adhere to some strict rules for everything to work:
- No blade is allowed to communicate directly with any other blade (we use the
EventHub
class to allow loosely-coupled, inter-blade communication). - Blades aren’t allowed to communicate directly with any particular app (we use a
ServiceRegistry
class, and a suite of services conforming to known interfaces to facilitate loosely-coupled, blade-to-app communication).
In this way, our clients are free to mix and match business components from the many example apps we create, providing them a springboard towards building the app they really want to create.
Interfaces To The Rescue
In Putting The Java back into Javascript I argued that interfaces are a better way of defining contracts between co-operating parties. Although this seems to be lost on many Javascripters, I believe the EventHub
provides further ammunition as to why the concept of interfaces is something worth simulating within Javascript.