Request-reply

The simplest pattern you'll see with messaging is the request-reply pattern. Also known as request-response, this is a method of retrieving data that is owned by another part of the application.

In many cases the sending of a command is an asynchronous operation. A command is fired and the application flow continues on. Because of this, there is no easy way to do things like lookup a record by ID. Instead one needs to send a command to retrieve a record and then wait for the associated event to be returned. A normal workflow looks like the following diagram:

Request-reply

Most events can be subscribed to by any number listeners. While it is possible to have multiple event listeners for a request-response pattern, it is unlikely and is probably not advisable.

We can implement a very simple request-response pattern here. In Westeros there are some issues with sending messages in a timely fashion. Without electricity, sending messages over long distances rapidly can really only be accomplished by attaching tiny messages to the legs of crows. Thus we have a Crow Messaging System.

We'll start with building out what we'll call the bus. A bus is simply a distribution mechanism for messages. It can be implemented in process, as we've done here, or out of process. If implementing it out of process, there are many options from 0mq, a lightweight message queue, to RabbitMQ, a more fully featured messaging system, to a wide variety of systems built on top of databases and in the cloud. Each of these systems exhibit some different behaviors when it comes to message reliability and durability. It is important to do some research into the way that the message distribution systems work as they may dictate how the application is constructed. They also implement different approaches to dealing with the underlying unreliability of applications:

class CrowMailBus {
  constructor(requestor) {
    this.requestor = requestor;
    this.responder = new CrowMailResponder(this);
  }
  Send(message) {
    if (message.__from == "requestor") {
      this.responder.processMessage(message);
    }
    else {
      this.requestor.processMessage(message);
    }
  }
}

One thing which is a potential trip-up is that the order in which messages are received back on the client is not necessarily the order in which they were sent. To deal with this it is typical to include some sort of a correlation ID. When the event is raised it includes a known ID from the sender so that the correct event handler is used.

This bus is a highly naïve one as it has its routing hard coded. A real bus would probably allow the sender to specify the address of the end point for delivery. Alternately, the receivers could register themselves as interested in a specific sort of message. The bus would then be responsible for doing some limited routing to direct the message. Our bus is even named after the messages it deals with – certainly not a scalable approach.

Next we'll implement the requestor. The requestor contains only two methods: one to send a request and the other to receive a response from the bus:

class CrowMailRequestor {
  Request() {
    var message = { __messageDate: new Date(),
    __from: "requestor",
    __corrolationId: Math.random(),
    body: "Hello there. What is the square root of 9?" };
    var bus = new CrowMailBus(this);
    bus.Send(message);
    console.log("message sent!");
  }
  processMessage(message) {
    console.dir(message);
  }
}

The process message function currently just logs the response but it would likely do more in a real world scenario such as updating the UI or dispatching another message. The correlation ID is invaluable for understanding which sent message the reply is related to.

Finally, the responder simply takes in the message and replies to it with another message:

class CrowMailResponder {
  constructor(bus) {
    this.bus = bus;
  }
  processMessage(message) {
    var response = { __messageDate: new Date(),
    __from: "responder",
    __corrolationId: message.__corrolationId,
    body: "Okay invaded." };
    this.bus.Send(response);
    console.log("Reply sent");
  }
}

Everything in our example is synchronous but all it would take to make it asynchronous is to swap out the bus. If we're working in node then we can do this using process.nextTick which simply defers a function to the next time through the event loop. If we're in a web context, then web workers may be used to do the processing in another thread. In fact, when starting a web worker, the communication back and forth to it takes the form of a message:

class CrowMailBus {
  constructor(requestor) {
    this.requestor = requestor;
    this.responder = new CrowMailResponder(this);
  }
  Send(message) {
    if (message.__from == "requestor") {
      process.nextTick(() => this.responder.processMessage(message));
    }
    else {
      process.nextTick(() => this.requestor.processMessage(message));
    }
  }
}

This approach now allows other code to run before the message is processed. If we weave in some print statements after each bus send, then we get output like the following:

Request sent!
Reply sent
{ __messageDate: Mon Aug 11 2014 22:43:07 GMT-0600 (MDT),
  __from: 'responder',
  __corrolationId: 0.5604551520664245,
  body: 'Okay, invaded.' }

You can see that the print statements are executed before the message processing as that processing happens on the next iteration.

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

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