Messaging Framework

Publish and subscribe to events

Overview

Blueprint has a built-in messaging framework that helps you to design a reactive application. The messaging framework is similar to events in Node.js, but events are handled asynchronously by default. This design approach allows controllers to fire off events without waiting for background tasks to complete—returning control as quick as possible to the client.

The messaging framework consists of messengers and listeners.

Object Events

The BaseObject class (see The Object Model) has methods for emitting and consuming events specific to the corresponding object instance. To emit an event from the object, use the emit(...args) method. Use either the once(name, ...args) or on(name, ...args) method to consume the corresponding event a single time or any time it is emitted, respectively. Here is an example for emitting and consuming an event on an object.

const { BO } = require ('@onehilltech/blueprint');

const Connection = BO.extend ({
  open (opts) {
    // do something..
    
    this.emit ('opened', this);
  }
});

// ...

let conn = new Connection ();
conn.on ('opened', conn => {
  console.log ('The connection is open');
});

As shown in the example above, the Connection.open() method emits the opened event on the object instance. We can then register to receive the event, which is illustrated with we call the conn.on() method. You can also use the conn.once() if you only want to listener to be notified once when a connection is opened, and not every time the connection is opened.

Application Events

Application events are events emitted through the Blueprint application. This should not be confused with the actual application event types. Unlike the object events discussed above, the implementation logic for both application emitters and consumers is different.

Implementing a Listener

All application event listeners are implemented in app/listeners/[EVENT_NAME] were EVENT_NAME is the name of the event you are listening. The application event listeners must also extend the Listener class in Blueprint. Here is an example of an application event listener responding to connection open events.

app/listeners/conn.open/console.js
const { Listener } = require ('@onehilltech/blueprint');

module.exports = Listener.extend ({
  handleEvent (conn) {
    console.log ('The connection is open');
  }
});

The event listener must implement the handleEvent() method. Similar to the object events, the handleEvent() method takes a variable number of parameters. This count depends on the number of parameters passed to the emit() method.

Emitting an Event

You emit an application event in a similar manner as emitting object events. The main different is you use blueprint.emit() method instead of directly emitting on the local object instance. Here is an example emitting the connection open event as an application event.

const blueprint = require ('@onehilltech/blueprint'); 
const { BO } = blueprint;

const Connection = BO.extend ({
  open (opts) {
    // do something..
    
    this.emit ('opened', this);
    blueprint.emit ('conn.opened', this);
  }
});

As shown in this example, the first emit() method is publishing the event a local object instance. The second emit() is publishing the event on through the Blueprint application.

The Application Instance

If you are implementing a Blueprint entity, such as a controller, listener, or policy, then you have access to the application instance as a data property. If you already have direct access to the application class, then you do not need to use blueprint.emit() and blueprint.on() or blueprint.once() to send and receive application events. Instead, you can use the emit(), once(), and on() method on the application instance.

Predefined Events

The following events are predefined to the Blueprint application.

  • blueprint.app.init - Called after the application is initialized.

  • blueprint.app.started - Called after the application is started.

Inter-Module Communication

One key feature of the messaging framework is entities can communicate with listeners in different Blueprint modules. We call this inter-module communication. This allows modules to be loosely coupled with other modules in an application. It also allows listening modules to react to module events without the source module knowing the listening module is dependent on its behavior.

Synchronous Messaging

Emitters

By default, emitting an event is an asynchronous operation. This means the client does not wait for all listeners to process the event before continuing. To make event processing a synchronous operation, the client to wait for the event to be processed. You do this by waiting for the Promise returned from the emit() method to be resolved.

conn.emit ('opened', this);                     // asynchronous

conn.emit ('opened', this).then (() => {        // synchronous

});

Listeners

The listener also can be asynchronous. If the listener is performing background processing, then it can return a Promise. If the emitter is asynchronous, then it will not continue until the listener's promise is either resolved or rejected.

const { Listener } = require ('@onehilltech/listener');

module.exports = Listener.extend ({
  handleEvent (conn) {
    return new Promise ((resolve, reject) => {
      // Add background processing code here
    });
  }
});

Last updated