The Domain Events Pattern

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.

Problem space

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:

Problem space

Dependency tree with domain event handlers and event raisers.

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.

Message and Handler Interfaces

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.

Multiple Event Handlers

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:

Multiple Event Handlers

Class structure for registering multiple event handlers per event.

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.

Firing an event

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.

Registering an Event handler for an Event

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.

Displaying error notifications

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:

Displaying error notifications

Screenshot of application showing an error notification

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.240.58