Friday, October 26, 2018

Taming Events: How to Use SoC to Organize Events.

If you're using any kind of eventing system in JavaScript, whether the built-in events or something more, you'll want to give some thought to how you organize the events. In the grain of self-documenting code, SoC, and containing risk I offer some suggestions on how to pull it off without pain.

What's an Eventing System?

Basically, an eventing system is a way to raise an event in one context (such as in a function or class) and listed for events in one or more different contexts. It's a great way to decouple your code, but you should be careful not to paint yourself into a corner. Here's an example of an event and a listener (you've no doubt seen this kind of thing before):

$input.on('change', handleInputChange);

This is an example of a JQuery listener. It listens to DOM events on an input that's represented by the variable `$input`. When the `change` event is raised by the DOM, the `handleInputChange` function is called in response. This is about as basic as it gets. Let's see how to raise an event programmatically.

$input.trigger('change');

This is the JQuery way to trigger an event. Any listeners such as `handleInputChange` will be called in order once the event loop comes around to them in turn.
Mozilla lists a boatload of native events on their developer site.
Some eventing systems, such as Backbone Radio (part of Marionette) or Redux, are built for handling custom events. You define the events, raise them in code, and listen to them. For example, Backbone Radio works a bit like this:

Backbone.Radio.channel('my-channel').once('my-event', handleMyEvent);

// elsewhere

Backbone.Radio.channel('my-channel').trigger('my-event', ..args);

And this will work just fine functionally. The event will trigger and the handler will handle it. You'll add more channels and more events. One day, you or a future developer will need to find out what channels are out there. One problem is, you can accidentally duplicate channels and events. Who knows what's listening to all those events. Organization to the rescue!

Organizing Events

In an event-driven system, you've got to be organized or you're system will essentially be running wild. By "organized", I mean three things:

  1. Use well-defined conventions. 
  2. Keep things in logical places. 
  3. Know where and how to find something easily.

Failure to organize will result in great difficulty resolving issues. For example, you might need to hunt and peck your way around the code to find what events are raised in the first place. Or, you may need to run the application in order to figure it out. Worse you might not be able to run the application in its current state. There are ways to solve these problems!

Make Channels/Queues Explicit

You can put all of the channels into one file. Then, simple require or include this file wherever you need to interact with events (trigger or listen to). This advice isn't just for Backbone Radio or even JS frameworks, it's true for any given event-driven system. If you're using a message queue via ruby or python, to name a few, you still want to separate where your queues are connected from where you're using them!

Here's an example using C# for sending messages to a queue:


// somewhere in a method 
 this._messageQueues.UserMessageQueue.QueueEvent('user-update', eventData);

OK, I admit that example is a bit generic. But, you can see how easy it is to raise an event with this system of organization.An instance of the class that holds the message queues is injected into the class through the constructor (not shown). Then the instance is used to get the specific message queue which is used to queue the event. This beats the alternative:

// in the same method
  SomeSpecificMessageQueueClient queue = new SomeSpecificMessageQueueClient('user');
  queue.Initialize();
  var topic = queue.GetTopic('user-update');
  topic.SetConfiguration(...)...more boilerplate code...
  
  ...finally, 30 lines of config code later...

  topic.QueueMessage(messageToQueue);

Alright, I concede once again to making this more complicated than necessary. We could have something like Queues.get('user') right? Of course we could! That's less boilerplate which is good. However, the problem still exists that all queues are adhoc in a string. Better to be explicit so that you have self-documenting code!
I still don't really like the fact that we're defining the event keys as strings. I'd like to be more explicit about that too so we know which events are raised throughout our code. In large code-bases this gets to be really important. Smaller code-bases can benefit too. We can make the events explicit too.

Make Events Explicit

By making the events explicit, we can easily see what events are raised and listened to in a system. We might be raising events for no reason, for example. When they're explicit, as in a method or function rather than a string, we can see how they're being listened to. Remember the `once` listener in one of the early examples? That registers the listener to listen one time to the event, then deregisters it after that first time. In this case, we would have an explicit method for the event such as `MyChannel.listenToMyEventOnce(listener)` and no methods on `MyChannel` named `listenToMyEvent`. From the class itself, we can easily see that there are no perpetual listeners to `MyEvent`. Whereas, if we have `once('my-event')` scattered throughout the codebase, we would have to search everywhere to find out. That's a long process. Besides, you might not have all the consumers of your events in the whole codebase when you're using something like a message queue. In fact, that's the whole benefit of message queues in the first place! Here's a more comprehensive code sample of what I'm advising here:

class UserEvents {
  private const BEFORE_SAVE = 'user-before-save';
  private _queue;

  constructor(Queue){
    super();
    this._queue = new Queue('user');
  }

  raiseBeforeSave(user) {
     this._queue.raise(BEFORE_SAVE, user);
  }
  listenToBeforeSave(callback) {
     this._queue.on(BEFORE_SAVE, callback);
  }
}

In this class (which is sort of Typemock-ish), I've defined all of the user events for now. The queue type is injected into the class so it can be swapped out for testing (or even adapted to use a different queue system). The consumers of UserEvents has no idea how it implements or even interacts with the queue. We've got all that detail contained within the domain-specific class "UserEvents".

Finally...

I just want to leave you with a final thought...this is all about self-documentation. It makes for easier programming and issue resolution without having to strap on a debugger or hunt through mounds of code. One addional benefit to containing all the eventing logic...you can see how easy it would be to add logging to the Queue right? Just pass in an instance of your custom LoggingQueue class that has the same methods but which logs each event. "raise" would first call the logger, it can easily log the handlers attached. And now you have a nice way to see the chain of asyncronous events that are always so much trouble to understand in an event-driven system! Happy Coding!