When it comes to writing scalable applications you need a front-end which packages up a message, which is sent along into a queue, and on the other side controllers read from the queue, perform work, and optionally return a message. (I like to use ‘nats’ for this.)
Common front-ends use a REST api, websocket, or grpc. They can all be used if desired, packaging up the same message which is passed along.
Along with a “request”, there is also optionally a “session”, as well as an authentication middle layer.
Here’s what I’ve standardized on:
- user allowed multiple websockets, but the websocket name must be unique
- if the websocket name already exists then drop the existing websocket and keep the new one
- multiple sessions are allowed per websocket, but the session name must be unique
- if websocket w/ session name already exists then the session create fails
- messages received will have a utc timestamp in milliseconds, this is the timestamp of the message, data within the message may have its own timestamp; everything is sent as soon as available, this may result in messages being received out of order
- all messages expect a session to be specified, defaulting to empty “”
- all message replies will include the session tacked on to the websocket name, e.g.: <demo>:<session>
- There are 5 types of messages:
- send, auth:
- sent as first message when using websocket
- sent with every message via header middle layer when using rest api
- send:
- send request into queue, there is only one ‘request’ type message, the request is unique due to the ‘topic’ field which should include a version number and name.verb e.g.: “v1.session.create”, this allows for protocol improvements later on optionally allowing for an older topic to continue to be used while being replaced by a newer version (without requiring some of the complexities of scale-to-zero)
- ‘id’ is optional, but is recommended, it will be included as ‘rid’ in the reply message; without an id there will be no way of matching an ‘rid’ with the original message when there are lots of messages
- recv, reply:
- includes an ‘rid’ if an ‘id’ was specified, this message is a reply to a request
- recv, event:
- does not include an ‘rid’, this is a message which is in response to an event, can be received at any time
- recv, system:
- does not include an ‘rid’, this is a message which is in response to a system event, which is rarely received, could be something like ‘os is shutting down’ or ‘maintenance will be performed tonight’
- send, auth:
Notes:
- Careful to make sure only one controller processes a message at a time, in general, and handle a timeout/retry in case the controller crashes or fails for some reason. (‘nats’ makes implementing these requirements easy.)
Examples of the 5 types of messages:
send, auth:
{
auth: {
type: "bearer",
token: "",
},
name: ""
}
send: {
id: "123",
session: "", // if session in use
topic: "",
body: {}
}
recv, reply:
{
rid: 123,
kind: "reply"
session: "", // if session in use
topic: "",
utc: z,
body: {},
status: {
code: 200,
detail: "",
error: "",
where: ""
}
}
recv, event:
{
kind: "event"
session: "", // if session in use
topic: "",
utc: z,
body: {},
}
recv, system:
{
kind: "system",
topic: "",
utc: z,
}
Standard messages:
// send, websocket authenticate
{
auth: {
type: "bearer",
token: "<redacted>",
},
name: "demo"
}
// send, create two sessions on the websocket
{
id: "1",
topic: "v1.session.create",
body: {"name":"aaa"}
}
{
id: "2",
topic: "v1.session.create",
body: {"name":"bbb"}
}
// send, message from one session to another
{
id: "3",
session: "aaa",
topic: "v1.message.create",
body: {"to":"websocket:demo:bbb","message":"message to a session on the specified websocket"}
}
// send, message from one session to all sessions on the specified websocket
{
id: "4",
session: "aaa",
topic: "v1.message.create",
body: {"to":"websocket:demo","message":"message to all sessions on the specified websocket"}
}
// send, message from one session to specified user across all websockets
{
id: "5",
session: "aaa",
topic: "v1.message.create",
body: {"to":"user:tloyd","message":"message to user on all websockets where user has authenticated"}
}
Standard replies:
// recv, reply:
{
rid: 4,
kind: "reply"
session: "aaa",
topic: "v1.message.create",
utc: "<millisecond_timestamp_int64>",
body: {},
status: {
code: 200,
detail: "",
error: "",
where: ""
}
}
Standard events:
// recv, event:
{
kind: "event"
session: "demo:bbb", // if session in use
topic: "v1.message.create",
utc: "<millisecond_timestamp_int64>",
body: {"from":"websocket:demo:aaa","message":"message to all sessions on the specified websocket"},
status: {
code: 200,
detail: "",
error: "",
where: ""
}
}
Standard systems:
recv, system:
{
kind: "system",
topic: "v1.os.restart",
utc: <millisecond_timestamp_int64>,
}
