Spectacles is a communication protocol and library ecosystem for coordinating microservices. Each application is built around a central message broker that coordinates messaging between services.
Each service has 2 properties that help it communicate with other services.
- Group name
- Client name
Services identify themselves by a globally unique group name that is shared amongst all instances of that service but is guaranteed to never be used by any other instance. This is usually a build- time constant, since a service should never change its identity during runtime.
For example, a service dedicated to handling incoming HTTP requests could have a group name of
INCOMING_HTTP
.
Every message is delivered at most once to a single client in every group.
Each instance of each service also identifies itself by a globally unique name. This name is usually generated randomly at runtime.
Every message broker has at least 3 concepts:
- Publish
- Call
- Consume
Every message emitted by the message broker has at least 2 concepts:
- Acknowledge
- Reply
When an event is generated and a response is not required, the client which created the event will "publish" it to the other clients. Each event has at least 2 attributes:
- Name
- Data
The event name is required to enable the receiving app to distinguish what data it should expect.
Publish must return the unique ID of this event emission.
Call behaves similarly to publish, except the calling client will wait for a single response from another client.
When a client consumes, it signals an intent to receive events for its group. The client must specify which events it intends to receive.
When a message is acknowledged, it is removed from pending state and will no longer be re-delivered to other consumers if the recipient client dies or times out.
When a client receives a "publish" event, it can choose to respond; if the source client is listening, it will receive the response.
Supported message brokers.
Specatcles' primary method of communication happens over Redis streams & pubsub. Most communication happens via streams, but pubsub is leveraged to simplify RPC responses.
XADD {event} * data {data}
- Publish
SUBSCRIBE {event}:{publish_id}
Due to Redis streams, the consumption process is two-fold. Each client must:
- Periodically run
XAUTOCLAIM
to ensure that all orphaned messages are handled - Use
XREADGROUP
to consume messages for its group
Both consumption processes emit events for the client to handle.
Loop forever:
XAUTOCLAIM {event} {group} {client} {max_operation_time} 0-0
XREADGROUP GROUP {group} {client} STREAMS {events.map ">"}
XACK {event} {group} {publish_id}
PUBLISH {event}:{publish_id} {data}
Spectacles communication via IPC. Intended for local deployments where one service only needs to communicate directly with another. Data is transmitted using JSON encoding:
{
"event": "event name",
"data": {}
}
Reference the Discord API documentation for information about packets received and sent. Packets must be JSON encoded.
Only the d
property of data received from the Discord gateway is sent to the message broker. Only OP code 0 data is sent, as the other OP codes are for internal WebSocket management.
Ex:
'MESSAGE_CREATE' => {
id: 'some id',
content: 'spectacles is awesome',
...
}
The full packet to send to the Discord gateway should be sent to the message broker. The shard ID is the queue name to publish to. Gateways must listen to events that correspond with shard IDs they own.
Ex:
'3' => {
op: 4,
d: {
guild_id: 'some id',
...
}
}
If only the guild ID is known, packets can be published as a SEND
event. The gateway which receives this packet is then reponsible for calculating the correct shard ID and re-publishing it in the above format. This format is available so that workers do not need to be aware of the shard count (required when calculating shard ID from guild ID).
'SEND' => {
guild_id: 'some id',
packet: {
op: 4,
d: {
guild_id: 'some id',
...
}
}
}