Skip to content

RFC: RFC JSONRPC as message format for eventbus bridges

Paulo Lopes edited this page Nov 25, 2021 · 5 revisions

Eventbus bridges communicate using JSON messages. The format of the messages is proprietary, which requires end users to use a custom piece of code to interact with the bridge or deduce the protocol by reading the provided library.

This RFC is about standardizing the message format using a well-known encoding JSON-RPC.

The bridge defines a few messages:

Basic messages

send

Sends a message to an eventbus address and eventually expects for a reply

// -->
{type: "send", address: "eventbusAddress", body: {}, headers: {}, replyAddress: "uuid"}
// <--
{type: "message", address: "uuid", body: {}, headers: {}, replyAddress: "uuid2", send: true}

Or send a message without expecting a reply:

// -->
{type: "send", address: "eventbusAddress", body: {}, headers: {}}

publish

Broadcasts a message to all listeners on the given address:

// -->
{type: "publish", address: "eventbusAddress", body: {}, headers: {}}

JSON-RPC alternative

For the JSON-RPC protocol, we should define a simple Message type.

export interface Message {
  headers? : Map<String, String | List<String>>,
  body     : any,
}

Headers: For transports that can have own header processing (like HTTP) the headers can be omited from the Message and consumed from the transport directly. When the headers are defined in the message object, they will be the sole source of headers, no merging of headers is expected or required.

This type shall be used in the params field in the next examples:

send

// -->
{jsonrpc: "2.0", method: "eventbusAddress", params: aMessage, id: "message-id"}
// <--
{jsonrpc: "2.0", "result": aMessage, "id": "message-id"}
// The reply address can be extracted from `aMessage.headers['X-Reply-Address']`

Or send a message without expecting a reply:

// -->
{jsonrpc: "2.0", method: "eventbusAddress", params: aMessage}

publish

Broadcasts a message to all listeners on the given address:

// -->
{jsonrpc: "2.0", method: "eventbusAddress", params: aMessage}

To signal the publishing intent, 2 things are required:

  1. no id
  2. the header message.headers['X-Publish'] = true

Bridge Actions

Bridge actions are complex operations that require knowledge on the internals of the event bus. Just like the Language Server Protocol, these intentions are marked with the prefix $/.

Clients and Servers may discard these commands at the penalty of not having push/streaming support.

We shall define a Registration interface:

export interface Registration {
  headers? : Map<String, String | List<String>>,
  address  : String,
}

register

Signals to the bridge that this client wants to register a consumer for a given address. On messages received at the bridge for the address, a JSON RPC notification shall be sent to the client node:

// current protocol
// -->
{type: "register", address: "eventbusAddress", headers: {}}

// JSON-RPC
// -->
{jsonrpc: "2.0", method: "$/register", params: aRegistration, id: "uuid"}
// <--
{jsonrpc: "2.0", result: "OK", id: "uuid"}

An improvement here is that we can get proper acknowledgement of the intent to register to an address.

unregister

This is the reverse of the previous action. Signals to the bridge the intent of not wanting to receive more notifications for the given address:

// current protocol
// -->
{type: "unregister", address: "eventbusAddress", headers: {}}

// JSON-RPC
// -->
{jsonrpc: "2.0", method: "$/register", params: aRegistration, id: "uuid"}
// <--
{jsonrpc: "2.0", result: "OK", id: "uuid"}

Connection management

To avoid connection breaks due to inactivity, the bridge also supports ping messages. For a ping message, a pong reply is expected.

We shall define the PingPong message as any. The pong reply should be the same as the input. This would allow clients to compute latency by sending a ping with a timestamp and on reply, diff the current time and the pong receiving timestamp (which is the original timestamp of departure).

// current protocol
// -->
{type: "ping"}
// <--
{type: "pong"}

// JSON-RPC
// -->
{jsonrpc: "2.0", method: "$/ping", params: aPingPong, id: "uuid"}
// <--
{jsonrpc: "2.0", result: aPingPong, id: "uuid"}

Service Proxies

Service Proxies over the event bus use a sligthly modified message format, this can be achived with JSON-RPC too:

// current protocol
// -->
{type: "send", address: "eventbusAddress", body: {}, headers: {action: "methodName"}, replyAddress: "uuid"}
// <--
{type: "message", address: "destinationAddress", body: {}, headers: {}, replyAddress: "uuid", send: true}

// JSON-RPC
// -->
{jsonrpc: "2.0", method: "eventbusAddress/methodName", params: aMessage, id: "message-id"}
// <--
{jsonrpc: "2.0", "result": aMessage, "id": "message-id"}

Error handling

Error handling would remain almost unmodified:

// current protocol
{type: "err", message: "access_denied|address_required|unknown_address|unknown_type"}

// JSON-RPC
{
  jsonrpc: "2.0",
  error: {
    code: -32601,
    message: "access_denied|address_required|unknown_address|unknown_type"
  },
  id: "uuid"
}
// where id is used to correlate the source request, order is not relevant in jsonrpc

The remaining JSON-RPC error codes are also supported:

code message meaning
-32700 Parse error Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
-32600 Invalid Request The JSON sent is not a valid Request object.
-32601 Method not found The method does not exist / is not available.
-32602 Invalid params Invalid method parameter(s).
-32603 Internal error Internal JSON-RPC error.
Clone this wiki locally