Most JavaScript frameworks have the concept of an event bus. An event bus is simply a method of publishing events to a global bus, so that other parts of your application that are subscribed to these events will receive a message, and be able to react to them. The use of an event-based architecture helps to decouple our applications, making them resilient to change and easier to test.
A Domain Event is an event that happens specific to our application domain. Something like "when an error occurs, log it to the console", or "when a menu button is clicked, change the sub-menu panel to reflect this option". A Domain Event can be raised anywhere in your code. Any class can register an event handler against this event, and will then be notified when this event is raised. There can be many event handlers for a single Domain Event.
Martin Fowler first blogged about the concept of a Domain Event in 2005 in a blog found at http://martinfowler.com/eaaDev/DomainEvent.html. Udi Dahan then showed how to implement a simple domain event pattern in C# in another blog found at http://www.udidahan.com/2009/06/14/domain-events-salvation/. Mike Hadlow also blogged about Separation of Concerns with Domain Events, and this blog can be found at http://mikehadlow.blogspot.com.au/2010/09/separation-of-concerns-with-domain.html.
Mike argues that a piece of code that raises an event should not be concerned with what happens after that—we should have separate handlers to handle these events—which are not coupled to anything actually raising the events.
While there are a number of JavaScript libraries that handle events—Postal for example—most of these libraries send strings or simple JavaScript objects as the message packet. There is no way of ensuring that the sender of the message is filling in all of the properties that the handler of the message is expecting. In other words, these messages are not strongly typed—and could easily cause runtime errors—by trying to fit a "square peg" message into a "round hole" event handler.
In this section, we will build a strongly typed Domain Event message bus, and show how both sides—the event raiser and the event handler—can ensure that the event that is raised has all of the properties that are expected in the event handler. We will also show how to ensure that the event handlers are written correctly—and registered correctly —so that events are delivered in a strongly typed manner.
Let's assume that we have the following business requirement: "If an error occurs, show the user an error message in a notification pop up. This pop up should show for two seconds and then fade away, allowing the user to continue working."
In our current application, there are a number of places where errors could occur—when loading JSON through the ContactCollection
, for instance—or when rendering a ContactItemView
. These errors could occur quite deep down in our class hierarchy. In order to achieve our stated requirements, we will need to handle these errors at the ContactViewApp
level. Consider the following diagram:
Our ContactViewApp
will register an event handler with TypeScriptTinyIoC
, specifying which event type it is interested in. When an event of this type is raised by any one of our modules, our message bus will direct the message to the correct handler, or group of handlers. In the preceding diagram, the ContactCollection
and the ContactItemView
classes are shown to be raising an ErrorEvent
via TypeScriptTinyIoC
.
There are two key sets of information that we need in order to register and raise strongly typed messages. The first is an interface describing the message itself, which is paired with its named interface. The second is an interface describing the message handler function, again which is paired with its named interface. Our TypeScript interface gives us compile-time checking of messages and handlers, and our named interfaces (implementing IInterfaceChecker
) give us runtime type checking of messages and handlers.
First up, the interfaces for our message are as follows:
interface IErrorEvent { Message: string; Description: string; } export class IIErrorEvent implements IInterfaceChecker { propertyNames: string [] = ["Message", "Description"]; className: string = "IIErrorEvent"; }
We start with the TypeScript interface IErrorEvent
. This interface has two properties, Message
and Description
, which are both strings. We then create our IIErrorEvent
class, which is an instance of our named interface – again with the propertyNames
array matching our TypeScript interface property names. The className
property is also set to be the name of the class, IIErrorEvent
, to ensure uniqueness.
The interfaces for our event handlers are then as follows:
interface IErrorEvent_Handler { handle_ErrorEvent(event: IErrorEvent); } export class IIErrorEvent_Handler implements IInterfaceChecker { methodNames: string[] = ["handle_ErrorEvent"]; className: string = "IIErrorEvent_Handler"; }
The TypeScript interface IErrorEvent_Handler
contains a single method, named handle_ErrorEvent
. This handler method has a single parameter, event
, which is again strongly typed to be our event interface, IErrorEvent
. We then construct a named interface called IIErrorEvent_Handler
, and match the TypeScript interface through the methodNames
array. Again, we provide a unique className
property for this named interface.
With these two interfaces and named interfaces in place, we can now create the actual ErrorEvent
class as follows:
export class ErrorEvent implements IErrorEvent { Message: string; Description: string; constructor(message: string, description: string) { this.Message = message; this.Description = description; } }
The class definition for ErrorEvent
implements the IErrorEvent
interface, thereby making it compatible with our event handler. Note the constructor
of this class. We are forcing users of this class to provide both a message
and description
parameter in the constructor – thereby using TypeScript compile-time checking to ensure that we construct this class correctly, no matter where it is used.
We can then create a class that implements the IErrorEvent_Handler
interface, which will receive the event itself. As a quick example, consider the following class:
class EventHandlerTests_ErrorHandler implements IErrorEvent_Handler { handle_ErrorEvent(event: IErrorEvent) { } }
This class implements the IErrorEvent_Handler
TypeScript interface, and therefore the compiler will force the class to define a handle_ErrorEvent
function with the correct signature, in order to receive messages.
To be able to register multiple events, and have multiple event handlers per event, we will need an array of events, each of which will, in turn, hold an array of handlers as follows:
Our TypeScriptTinyIoC
class will have an array called events
, which uses the name of the event as its key. This name will be drawn from our named interface for the event – again because TypeScript interfaces are compiled away. To help with managing multiple event handlers per event, we will create a new class called EventHandlerList
that will facilitate the registration of multiple event handlers. An instance of this EventHandlerList
class will be stored in our events
array for each named event that we have registered.
Let's start with this list of event handlers, and implement our EventHandlerList
class. At this stage, all we need is an internal array to store handlers, named eventHandlers
, along with a registerHandler
function as follows:
class EventHandlerList { eventHandlers: any[] = new Array(); registerHandler(handler: any, interfaceType: { new (): IInterfaceChecker }) { } }
The registerHandler
function is again using the { new(): IInterfaceChecker }
syntax for the interfaceType
argument, thereby allowing us to use a type name for this function call. A quick unit test is as follows:
import iee = require("../app/events/ErrorEvent"); class EventHandlerTests_ErrorHandler implements iee.IErrorEvent_Handler { handle_ErrorEvent(event: iee.IErrorEvent) { } } describe("/tests//EventHandlerTests.ts", () => { var testHandler: EventHandlerTests_ErrorHandler; beforeEach(() => { testHandler = new EventHandlerTests_ErrorHandler(); }); it("should register an event Handler", () => { var eventHandlerList = new EventHandlerList(); eventHandlerList.registerHandler(testHandler, iee.IIErrorEvent_Handler); expect(eventHandlerList.eventHandlers.length).toBe(1); }); });
We start this test with an import
statement for our event classes, and then a class named EventHandlerTests_ErrorHandler
. This class will be used as a registered event handler just for this test suite. The class implements the iee.IErrorEvent_Handler
and, as such, will generate a compile error if we do not have a handle_ErrorEvent
function that accepts an IErrorEvent
as its only parameter. Just by using TypeScript interfaces, we have already ensured that this class has the correct function name and function signature to accept ErrorEvent
messages.
Our test then starts by declaring a variable named testHandler
to store an instance of our EventHandlerTests_ErrorHandler
class. The beforeEach
function will create this instance, and assign it to our testHandler
variable. The test itself then creates an instance of the EventHandlerList
class, calls the registerHandler
, and then expects the length
of the internal eventHandlers
property to be the value of one.
Note again the syntax of the call to registerHandler
. We are passing in our testHandler
instance as the first argument, and then specifying the named interface IIErrorEvent_Handler
class type. As we saw with the service locator pattern, we are again using the same class name syntax for our named interface, instead of having to call new()
.
Let's now fill in the code to make the test pass:
class EventHandlerList { eventHandlers: any[] = new Array(); registerHandler(handler: any, interfaceType: { new (): IInterfaceChecker }) { var interfaceChecker = new InterfaceChecker(); if (interfaceChecker.implementsInterface( handler, interfaceType)) { this.eventHandlers.push(handler); } else { var interfaceExpected = new interfaceType(); throw new Error( "EventHandlerList cannot register handler of " + interfaceExpected.className); } } }
Our registerHandler
function firstly creates an instance of the InterfaceChecker
class, and then calls implementsInterface
to make sure, at runtime, that the handler object that is passed in does indeed have all of the method names defined by our named interface. If the implementsInterface
function returns true
, we can simply push this handler onto our internal array.
If the handler does not implement the named interface, we throw an error. For completeness, this error contains the className
property of the named interface, so we first have to new up an instance of this named interface class, before we can extract the className
property.
Let's now create a test that will deliberately fail our implementsInterface
check and ensure that an error is in fact thrown:
class No_ErrorHandler { } it("should throw an error with the correct className", () => { var eventHandlerList = new EventHandlerList(); expect(() => { eventHandlerList.registerHandler(new No_ErrorHandler(), iee.IIErrorEvent_Handler); }).toThrow(new Error( "EventHandlerList cannot register handler of IIErrorEvent_Handler" )); });
We start with the class definition of the No_ErrorHandler
class that obviously does not implement our named interface. Our test then sets up the EventHandlerList
class, and calls the registerHandler
function, using a new instance of the No_ErrorHandler
class, and our IIErrorEvent_Handler
named interface. We are then expecting a specific error message— one that should include the name of our named interface, IIErrorEvent_Handler
.
We can now turn our attention to raising an event. To do this, we will need to know what the actual function name of the event handler is. We will make a slight change to our EventHandlerList
, and pass in the event name to the constructor as follows:
class EventHandlerList { handleEventMethod: string; constructor(handleEventMethodName: string) { this.handleEventMethod = handleEventMethodName; } raiseEvent(event: any) { } }
Our constructor is now expecting a handleEventMethodName
as a required parameter, and we are storing this in a property named handleEventMethod
. Remember that all of the handlers that are registered with an instance of this class are responding to the same event – and as such will all have the same method name – enforced by the TypeScript compiler. We have also defined a raiseEvent
function, and since we do not know what event this class will be handling, the event is of type any
.
Now, we can create a unit test that will fail, as the raiseEvent
function is not actually doing anything as yet. Before we do this, lets update our test handler class, EventHandlerTests_ErrorHandler,
to store the last event fired in a property that we can access later:
class EventHandlerTests_ErrorHandler implements iee.IErrorEvent_Handler { LastEventFired: iee.IErrorEvent; handle_ErrorEvent(event: iee.IErrorEvent) { this.LastEventFired = event; } }
We have updated this class definition with a property named LastEventFired
, and set this property inside the handle_ErrorEvent
function. With this change in place, when an event is fired, we can interrogate the LastEventFired
property to see what event was fired last. Let's now write a test that calls the raiseEvent
method:
it("should fire an event", () => { var eventHandlerList = new EventHandlerList('handle_ErrorEvent'), eventHandlerList.registerHandler(testHandler, iee.IIErrorEvent_Handler); eventHandlerList.raiseEvent( new iee.ErrorEvent("test", "test")); expect(testHandler.LastEventFired.Message).toBe("test"); });
We start with a variable named eventHandlerList
that holds an instance of our EventHandlerList
class, and pass in the name of the function to be called via the constructor. We then call registerHandler
with this testHandler
instance. Now, we can call the raiseEvent
function, passing in a new ErrorEvent
. As the constructor of our ErrorEvent
class requires two parameters, we have just passed in "test"
for each of these arguments. Finally, we are expecting that the LastEventFired
property of our event handler to be set correctly. Running our test at this stage will fail, so let's implement the raiseEvent
method on our EventHandlerList
class as follows:
raiseEvent(event: any) { var i, len = 0; for (i = 0, len = this.eventHandlers.length; i < len; i++) { var handler = this.eventHandlers[i]; handler[this.handleEventMethod](event); } }
The implementation of this raiseEvent
function is relatively simple. We just iterate through our eventHandlers
array, and then get a reference to each of the event handlers using an index. The line to note here is how we execute the handler function: handler[this.handleEventMethod](event)
. This takes advantage of JavaScript's ability to calling a function using a string value that matches the function's name. In our tests, this would be equivalent to handler['handle_ErrorEvent'](event)
, which in JavaScript is equivalent to handler.handle_ErrorEvent(event)
—an actual call to the handler function. With this JavaScript magic in place, our events are being fired, and our unit tests run through correctly.
Now that we have a working, tested class to manage multiple event handlers responding to a specific event, we can turn our attention back to the TypeScriptTinyIoC
class.
As we did for our Service Locator pattern, we will need to register an instance of an object to handle a specific event. The method signature for registering our event handler will look like this:
public static registerHandler( handler: any, handlerInterface: { new (): IInterfaceChecker }, eventInterface: { new (): IInterfaceChecker }) { }
This registerHandler
function takes three arguments. The first is the instance of the object implementing the handler. The second argument is the named interface class for our handler—so that we can check this class at runtime to ensure that it implements the handler interface. The third argument is the named interface for the event itself. This register
function is also what binds an event to its handler.
Before we put together a unit test, we will need another static function to raise an event:
static raiseEvent(event: any, eventInterface: { new (): IInterfaceChecker }) { }
This raiseEvent
function on the TypeScriptTinyIoC
class will call the raiseEvent
function on the EventHandlerList
class instance for this event. We will also do an interfaceChecker
test here, in order to ensure that the event being raised matches our named interface class for the event—before we actually raise the event.
Now for our unit test:
it("should register an event handler with TypeScriptTinyIoC and fire an event", () => { TypeScriptTinyIoC.registerHandler(testHandler, iee.IIErrorEvent_Handler, iee.IIErrorEvent); TypeScriptTinyIoC.raiseEvent( new iee.ErrorEvent("test", "test"), iee.IIErrorEvent); expect(testHandler.LastEventFired.Message).toBe("test"); });
This test is very similar to the test that we wrote for our EventHandlerList
class, except we are calling the registerHandler
and raiseEvent
methods on the TypeScriptTinyIoC
class, instead of a specific EventHandlerList
. With this failing test in place, we can now fill out the registerHandler
and raiseEvent
functions as follows:
static events: EventHandlerList[] = new Array<EventHandlerList>(); public static registerHandler( handler: any, handlerInterface: { new (): IInterfaceChecker }, eventInterface: { new (): IInterfaceChecker }) { var eventInterfaceInstance = new eventInterface(); var handlerInterfaceInstance = new handlerInterface(); var handlerList = this.events[eventInterfaceInstance.className]; if (handlerList) { handlerList.registerHandler(handler, handlerInterface); } else { handlerList = new EventHandlerList( handlerInterfaceInstance.methodNames[0]); handlerList.registerHandler(handler, handlerInterface); this.events[eventInterfaceInstance.className] = handlerList; } }
Firstly, we have added a static property called events
, which is an array of EventHandlerList
instances. We will add to this array using the className
of our named event interface as a key. Our registerHandler
function firstly creates instances of both named interface classes that are passed in via the handlerInterface
and eventInterface
arguments. We are then checking to see whether our internal array already has an EventHandlerList
instance for this event, keyed via the className
property of our named event interface. If we have an entry already, we can simply call the registerHandler
function on the existing EventHandlerList
instance. If this event has not been registered, we simply create a new instance of an EventHandlerList
class, call registerHandler
, and then add this entry to our internal array.
Note how we figured out what the actual name of the event handler function call is. We are simply using the first method name found in our method names array: handlerInterfaceInstance.methodNames[0]
, which will return a string. In our samples, this would return the 'handle_ErrorEvent'
string, which is the method name that we will need to invoke when invoking handler functions for an event.
Next, we can focus on the raiseEvent
function:
static raiseEvent(event: any, eventInterface: { new (): IInterfaceChecker }) { var eventChecker = new InterfaceChecker(); if (eventChecker.implementsInterface(event, eventInterface)) { var eventInterfaceInstance = new eventInterface(); var handlerList = this.events[eventInterfaceInstance.className]; if (handlerList) { handlerList.raiseEvent(event); } } }
This function first creates an instance of an InterfaceChecker
class, and then ensures that the event being raised conforms to the named interface that we provide as the second parameter. Again, this is a runtime type check to ensure that the event we are attempting to raise is in fact of the correct type. If the event is valid, we fetch the instance of the EventHandlerList
class that is registered for this event, and then call its raiseEvent
function.
Our strongly typed Domain Event mechanism is now complete. We are using both compile-time TypeScript interface checking, and runtime type checking in two ways. Firstly, when registering a handler, we do an interface check, and then when we fire an event, we do another interface check. This means that both sides—registering and firing—of events are strongly typed, both at compile time and also at runtime.
Now that we have our TypeScriptTinyIoC
event mechanism in place, we can focus on solving the business problem of showing error notifications when errors occur. Notify is a jQuery plugin that suits our needs perfectly (http://notifyjs.com/). We could install the JavaScript library from NuGet (Install the jQuery.notify
package), but the default version of this package relies on another package named Bootstrap for its styling. Notify, however, also provides an option on their website to download a custom notify.js script that has all of these styles built-in to the library. We will use this custom version, as our project is not using the Bootstrap package.
The definition file for Notify can be downloaded from DefinitelyTyped (https://github.com/borisyankov/DefinitelyTyped/tree/master/notify). At the time of writing, however, there seems to be two versions of the Notify library, one named Notify and the other named Notify.js. Use the Notify version as it seems to be more up to date.
To simulate an error, let's tag onto the ContactItemView onClicked
function, where we are currently executing a flip, and raise a dummy error whenever someone clicks on one of our contact links:
onClicked() { this.$el.flip({ direction: 'tb', speed : 200 }); var errorEvent = new iee.ErrorEvent( "Dummy error message", this.model.Name); TypeScriptTinyIoC.raiseEvent(errorEvent, iee.IIErrorEvent); }
After our call to flip, we are simply creating an instance of the ErrorEvent
class, with its two required parameters. We then call the raiseEvent
function on TypeScriptTinyIoC
with this errorEvent
instance, and the named interface for the type of event that we are raising. It's as simple as that.
Now, we can modify our ContactViewApp
to register a handler for this event as follows:
import iee = require("tscode/app/events/ErrorEvent"); export class ContactViewApp implements iee.IErrorEvent_Handler { constructor() { TypeScriptTinyIoC.registerHandler(this, iee.IIErrorEvent_Handler, iee.IIErrorEvent); } run() { } contactCollectionLoaded(model, response, options) { } contactCollectionError(model, response, options) { } handle_ErrorEvent(event: iee.IErrorEvent) { $.notify("Error : " + event.Message + " " + event.Description); } }
Here, have made a few changes to our ContactViewApp
class. Firstly, we implement the IErrorEvent_Handler
TypeScript interface, which will force us to include the handle_ErrorEvent
function within our class. We have also defined a constructor
, and within this, we are registering the class instance as a handler using our two named interfaces: IIErrorEvent_Handler
, and IIErrorEvent
.
Within the handle_ErrorEvent
function, we are calling $.notify
—the Notify jQuery plugin. Note that the type of the event
argument passed into the handle_ErrorEvent
function, is of type IErrorEvent
. This means that we can safely use any properties or methods of the IErrorEvent
interface within our event handler function, as we have already ensured, during event raising, that this event implements the interface correctly.
Our call to Notify is just using a message that is built up from our ErrorEvent
. The following screenshot shows the results of this Notify call:
The implementation of this Service Locator pattern and the strongly typed Domain Events pattern that we have worked through in this chapter are available on the GitHub project typescript-tiny-ioc (https://github.com/blorkfish/typescript-tiny-ioc). This project has further code samples as well as a full suite of unit tests for both AMD and normal JavaScript usage.
3.149.240.58