Middleware

One of the most distinctive patterns in Node.js is definitely middleware. Unfortunately it's also one of the most confusing for the inexperienced, especially for developers coming from the enterprise programming world. The reason for the disorientation is probably connected with the meaning of the term middleware, which in the enterprise architecture's jargon represents the various software suites that help to abstract lower level mechanisms such as OS APIs, network communications, memory management, and so on, allowing the developer to focus only on the business case of the application. In this context, the term middleware recalls topics such as CORBA, Enterprise Service Bus, Spring, JBoss, but in its more generic meaning it can also define any kind of software layer that acts like a glue between lower level services and the application (literally the software in the middle).

Express (http://expressjs.com) popularized the term middleware in the Node.js world, binding it to a very specific design pattern. In express, in fact, a middleware represents a set of services, typically functions, that are organized in a pipeline and are responsible for processing incoming HTTP requests and relative responses. An express middleware has the following signature:

Where req is the incoming HTTP request, res is the response, and next is the callback to be invoked when the current middleware has completed its tasks and that in turn triggers the next middleware in the pipeline.

Examples of the tasks carried out by an express middleware are as the following:

If we think about it, these are all tasks that are not strictly related to the main functionality of an application, rather, they are accessories, components providing support to the rest of the application and allowing the actual request handlers to focus only on their main business logic. Essentially, those tasks are sof tware in the middle.

The technique used to implement middleware in express is not new; in fact, it can be considered the Node.js incarnation of the Intercepting Filter pattern and the Chain of Responsibility pattern. In more generic terms, it also represents a processing pipeline, which reminds us about streams. Today, in Node.js, the word middleware is used well beyond the boundaries of the express framework, and indicates a particular pattern whereby a set of processing units, filters, and handlers, under the form of functions are connected to form an asynchronous sequence in order to perform preprocessing and postprocessing of any kind of data. The main advantage of this pattern is flexibility; in fact, this pattern allows us to obtain a plugin infrastructure with incredibly little effort, providing an unobtrusive way for extending a system with new filters and handlers.

The following diagram shows the components of the middleware pattern:

Middleware as a pattern

The essential component of the pattern is the Middleware Manager, which is responsible for organizing and executing the middleware functions. The most important implementation details of the pattern are as follows:

There is no strict rule on how the data is processed and propagated in the pipeline. The strategies include:

The right approach that we need to take depends on the way the Middleware Manager is implemented and on the type of processing carried out by the middleware itself.

Let's now demonstrate the pattern by building a middleware framework around the ØMQ (http://zeromq.org) messaging library. ØMQ (also known as ZMQ, or ZeroMQ) provides a simple interface for exchanging atomic messages across the network using a variety of protocols; it shines for its performances, and its basic set of abstractions are specifically built to facilitate the implementation of custom messaging architectures. For this reason, ØMQ is often chosen to build complex distributed systems.

Note

In Chapter 8, Messaging and Integration Patterns, we will have the chance to analyze the features of ØMQ in more detail.

The interface of ØMQ is pretty low-level, it only allows us to use strings and binary buffers for messages, so any encoding or custom formatting of data has to be implemented by the users of the library.

In the next example, we are going to build a middleware infrastructure to abstract the preprocessing and postprocessing of the data passing through a ØMQ socket, so that we can transparently work with JSON objects but also seamlessly compress the messages traveling over the wire.

The first step to build a middleware infrastructure around ØMQ is to create a component that is responsible for executing the middleware pipeline when a new message is received or sent. For the purpose, let's create a new module called zmqMiddlewareManager.js and let's start defining it:

This first code fragment defines a new constructor for our new component. It accepts a ØMQ socket as an argument and:

The next method of the ZmqMiddlewareManager prototype is responsible for executing the middleware when a new message is sent through the socket:

This time the message is processed using the filters in the outboundMiddleware list and then passed to socket.send() for the actual network transmission.

Now, we need a small method to append new middleware functions to our pipelines; we already mentioned that such a method is conventionally called use():

Each middleware comes in pairs; in our implementation it's an object that contains two properties, inbound and outbound, that contain the middleware functions to be added to the respective list.

It's important to observe here that the inbound middleware is pushed to the end of the inboundMiddleware list, while the outbound middleware is inserted at the beginning of the outboundMiddleware list. This is because complementary inbound/outbound middleware functions usually need to be executed in an inverted order. For example, if we want to decompress and then deserialize an inbound message using JSON, it means that for the outbound, we should instead first serialize and then compress.

Now, it's time to define the core of our component, the function that is responsible for executing the middleware:

The preceding code should look very familiar; in fact, it is a simple implementation of the asynchronous sequential iteration pattern that we learned in Chapter 2, Asynchronous Control Flow Patterns. Each function in the middleware array received in input is executed one after the other, and the same arg object is provided as an argument to each middleware function; this is the trick that makes it possible to propagate the data from one middleware to the next. At the end of the iteration, the finish() callback is invoked.

We are now ready to use the middleware infrastructure that we just created. To do that, we are going to build a very simple application, with a client sending a ping to a server at regular intervals and the server echoing back the message received.

From an implementation perspective, we are going to rely on a request/reply messaging pattern using the req/rep socket pair provided by ØMQ (http://zguide.zeromq.org/page:all#Ask-and-Ye-Shall-Receive). We will then wrap the sockets with our zmqMiddlewareManager to get all the advantages from the middleware infrastructure that we built, including the middleware for serializing/deserializing JSON messages.

Let's start by creating the server side (server.js). In the first part of the module we initialize our components:

In the preceding code, we loaded the required dependencies and bind a ØMQ 'rep' (reply) socket to a local port. Next, we initialize our middleware:

We created a new ZmqMiddlewareManager object and then added two middlewares, one for compressing/decompressing the messages and another one for parsing/serializing JSON messages.

Now we are ready to handle a request coming from the client, we will do this by simply adding another middleware, this time using it as a request handler:

Since this last middleware is defined after the zlib and json middlewares, we can transparently use the decompressed and deserialized message that is available in the message.data variable. On the other hand, any data passed to send() will be processed by the outbound middleware, which in our case will serialize then compress the data.

On the client side of our little application, 'client.js', we will first have to initiate a new ØMQ 'req' (request) socket connected to the port 5000, the one used by our server:

Then, we need to set up our middleware framework in the same way that we did for the server:

Next, we create an inbound middleware to handle the responses coming from the server:

In the preceding code, we simply intercept any inbound response and print it to the console.

Finally, we set up a timer to send some ping requests at regular intervals, always using the zmqMiddlewareManager to get all the advantages of our middleware:

We can now try our application by first starting the server:

We can then start the client with the following command:

At this point, we should see the client sending messages and the server echoing them back.

Our middleware framework did its job; it allowed us to decompress/compress and deserialize/serialize our messages transparently, leaving the handlers free to focus on their business logic!