Faye

Simple pub/sub messaging for the web

Internal architecture

If you’re developing Faye, the following gives an overview of the project’s architecture. Both the Ruby and JavaScript versions share the same internal structure and their implementations are very similar. Faye is based on the Bayeux protocol, and is compatible with the reference implementation of Bayeux provided by the cometD project.

Messages flow through the system according to this diagram:

The components that make up the messaging stack are described below. They are designed to be modular such that each layer is easily swappable if the need arises.

Storage

The storage layer is the base of the whole stack, and as its name suggests it stores the state of the Faye service. This state includes a list of active client IDs, which channels each client is subscribed to, and any queued messages waiting to be delivered to clients.

State is stored in the server’s memory if using the memory engine (the default), and in a Redis database if using the redis engine.

Engine

The engine is an object that provides an abstract API on top of the storage layer. It implements all the core operations of the Faye service, such as registering new clients, storing subscriptions and routing messages. Faye currently has two engines available:

  • memory – stores state in the server process’s memory
  • redis – stores state in a Redis database, can run multiple server instances off one DB

All engine types provide the same API so they can be easily swapped out for a different backend implementation. The engine’s operations assume that all necessary validation has been done on incoming data further up the stack so the implementations can be kept small and simple.

Server

The server implements the Bayeux messaging protocol, providing the handshake, connect, disconnect, subscribe, unsubscribe and publish operations. It delegates execution of these operations to the Engine; the Server’s job is simply to wrap the operations with the Bayeux protocol and validate incoming messages. This separation makes it easy to swap out the backend implementation while keeping the protocol layer consistent.

The Server class does not know anything about HTTP or other network transports, it simply provides an object-based implementation of Bayeux.

Server-side extensions

These are components written by the user that intercept messages that flow in and out of the Server. Incoming extensions apply when a message comes in to the server from the Internet, and outgoing extensions apply when a message is being sent back out to the client. The user can change the messages’ data, and add errors to stop the Server processing them.

Adapter

The Adapter, as implemented by the NodeAdapter and RackAdapter classes, exposes the Server’s interface over HTTP. It is responsible for serializing and deserialzing messages as JSON and accepting connections over various flavours of HTTP transport:

  • Persistent connections using WebSocket
  • Long-polling via HTTP POST
  • Cross Origin Resource Sharing
  • Callback-polling via JSON-P

Transport

The Transport classes implement the client side of the network transports supported by the server-side Adapter. Their job is to accept messages from the Client, serialize them and send them to the server. When responses arrive they are deserialized and given to the Client.

Transport objects are also responsible for detecting and recovering from network errors and server restarts using the best strategy available. For example, the WebSocket transport can immediately detect disconnections, whereas the JSON-P transport has to rely on timeouts.

Client-side extensions

These are components written by the user that intercept messages that flow in and out of the Client. Outgoing extensions apply when a message is sent by the Client, and incoming extensions apply when a message arrives from the server.

Client

This is the component that the user interacts with the most, and provides the interface through which subscriptions are registered and messages are published. The Client implements the client side of the Bayeux protocol, and presents a slightly higher-level interface to the user. For example, the user does not have to initiate handshakes or connections, this is done automatically.

Adding multiple subscriptions to the same channel only results in one subscribe message going to the server, and similarly an unsubscribe message is not sent until all listeners have been removed from the channel. The client’s subscribe/unsubscribe interface deals with managing these subscriptions and distributing messages to the right listeners as new messages arrive from the server.

Clustering

Some engines provided by Faye (for example the redis engine) support clustering, i.e. they let you run a single Faye service across multiple front-end web servers. These engines are slower since they must perform I/O to external services, but they let you scale your Faye service if you outgrow the network connection limit of one machine.

In clustered engines, the Storage layer (for example a Redis database server) is shared and several Engine+Server+Adapter stacks can be run on top of it. The server stacks are stateless: a Client should be able to connect to any of them at any time and it should behave as though interacting with a single service.