-
Notifications
You must be signed in to change notification settings - Fork 35
RFC: RFC JSONRPC as message format for eventbus bridges
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:
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: {}}
Broadcasts a message to all listeners on the given address:
// -->
{type: "publish", address: "eventbusAddress", body: {}, headers: {}}
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:
// -->
{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}
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:
- no
id
- the header
message.headers['X-Publish'] = true
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,
}
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.
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"}
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 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 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. |