Protocol 4.0 (rev. 1)
December 12, 2021 · View on GitHub
This protocol is used to communicate between the Moleculer nodes.
Concept
Subscriptions
After the client is connected to the message broker (NATS, Redis, MQTT), it subscribes to the following topics:
| Type | Topic name |
|---|---|
| Event | MOL.EVENT.<nodeID> |
| Event (balanced) | MOL.EVENTB.<event> |
| Request | MOL.REQ.<nodeID> |
| Request (balanced) | MOL.REQB.<action> |
| Response | MOL.RES.<nodeID> |
| Discover | MOL.DISCOVER |
| Discover (targeted) | MOL.DISCOVER.<nodeID> |
| Info | MOL.INFO |
| Info (targeted) | MOL.INFO.<nodeID> |
| Heartbeat | MOL.HEARTBEAT |
| Ping | MOL.PING |
| Ping (targeted) | MOL.PING.<nodeID> |
| Pong | MOL.PONG.<nodeID> |
| Disconnect | MOL.DISCONNECT |
If
namespaceis defined, the topic prefix isMOL-<namespace>instead ofMOL. E.g.:MOL-dev.EVENTwhen the namespace isdev.
Variables in topic names:
<namespace>- Namespace from broker options<nodeID>- Target nodeID<action>- Action name. E.g.:posts.find<group>- Event group name. E.g.:users<event>- Event name. E.g.:user.created
Discovering
After subscribing, the transporter broadcasts a DISCOVER packet. In response to this, all connected nodes send back INFO packet to the sender node. From these responses, the client builds its own service registry. At last, the client broadcasts own INFO packet to all other nodes.

Heartbeat
The client has to broadcast HEARTBEAT packets periodically. The period value comes from broker options (heartbeatInterval). The default value is 5 secs.
If the client does not receive HEARTBEAT for heartbeatTimeout seconds from a node, marks it broken and doesn't route requests to this node.

Request-reply
When you call the broker.call method, the broker sends a REQUEST packet to the targeted node. It processes the request and sends back a RESPONSE packet to the requester node.

Event
When you call the broker.emit method, the broker sends an EVENT packet to the subscriber nodes. The broker groups & balances the subscribers, so only one instance per service receives the event. If you call the broker.broadcast method, the broker sends an ĘVENT packet to all subscriber nodes. It doesn't balance the subscribers.

Ping-pong
When you call the broker.ping method, the broker sends a PING packet to the targeted node. If node is not defined, it sends to all nodes. If the client receives the PING packet, sends back a PONG response packet. If it receives, broker broadcasts a local $node.pong event to the local services.

Disconnect
When a node is stopping, it broadcasts a DISCONNECT packet to all nodes.

Protocol messages
DISCOVER
When the transporter established the connection with the message broker, it broadcasts a DISCOVER message to all other nodes.
Topic name:
MOL.DISCOVER(if broadcasts)MOL.DISCOVER.node-1(if sent only tonode-1)MOL-dev.DISCOVER(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
Example with JSON serializer
{
"ver": "4",
"sender": "node-100"
}
INFO
When the node receives an INFO packet, it sends an INFO packet which contains all mandatory information about the nodes and the loaded services.
Topic name:
MOL.INFO(if broadcasts)MOL.INFO.node-1(if sent only tonode-1)MOL-dev.INFO(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
services | object | ✔ | Services list. (*) |
config | object | ✔ | Client configuration. (*) |
instanceID | string | ✔ | Instance ID |
ipList | [string] | ✔ | IP address list of node |
hostname | string | ✔ | Hostname of node |
client | object | ✔ | Client information |
client.type | string | ✔ | Type of client implementation(nodejs, java, go) |
client.version | string | ✔ | Client (Moleculer) version |
client.langVersion | string | ✔ | NodeJS/Java/Go version |
metadata | object | ✔ | Node-specific metadata. (*) |
(*) In case of schema-based serializers, the field value is encoded to JSON string.
Example with JSON serializer
{
"services": [
{
"name": "$node",
"settings": {},
"metadata": {},
"actions": [],
"events": {}
},
{
"name": "greeter",
"settings": {},
"metadata": {},
"actions": [],
"events": {}
}
],
"ipList": ["10.35.0.34"],
"hostname": "moleculer-server",
"client": {
"type": "nodejs",
"version": "0.14.0-beta3",
"langVersion": "v12.10.0"
},
"config": {},
"instanceID": "ee21e97d-9fd0-4d7e-a303-70b1605f477f",
"metadata": {},
"seq": 2,
"ver": "4",
"sender": "nodeID-1"
}
HEARTBEAT
Topic name:
MOL.HEARTBEATMOL-dev.HEARTBEAT(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
cpu | double | ✔ | Current CPU utilization (percentage). |
Example with JSON serializer
{
"ver": "4",
"sender": "node-100",
"cpu": 13.5
}
REQUEST
Topic name:
MOL.REQ.node-2MOL.REQB.<action>(if built-in balancer is disabled)MOL-dev.REQ.node-2(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
id | string | ✔ | Context ID. |
action | string | ✔ | Action name. E.g.: posts.find |
params | object | ctx.params object. (**) | |
paramsType | enum | ✔ | Data type of ctx.params. (***) |
meta | object | ✔ | ctx.meta object. (*) |
timeout | double | ✔ | Request timeout (distributed) in milliseconds. |
level | int32 | ✔ | Level of request. |
tracing | boolean | ✔ | Need to send tracing events. |
parentID | string | Parent context ID. | |
requestID | string | Request ID from ctx.requestID. | |
caller | string | Action name of the caller. | |
stream | boolean | ✔ | Stream request. |
seq | int32 | Stream sequence number. |
(*) In case of schema-based serializers, the field value is encoded to JSON string.
(**) In case of schema-based serializers, the field value is encoded to JSON string and transferred as binary data.
(***) Used only in
ProtoBuf,Avro,Thriftor any other schema-based serializer to detect the original type of data.
Example with JSON serializer
{
"id": "41238213-da6b-4313-9909-e6edd0e40a96",
"action": "greeter.hello",
"params": {},
"meta": {},
"timeout": 10000,
"level": 1,
"tracing": null,
"parentID": null,
"requestID": "41238213-da6b-4313-9909-e6edd0e40a96",
"caller": null,
"stream": false,
"ver": "4",
"sender": "nodeID-1"
}
RESPONSE
Topic name:
MOL.RES.node-1MOL-dev.RES.node-1(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
id | string | ✔ | Context ID (from REQUEST). |
success | boolean | ✔ | Is it a success response? |
data | object | Response data if success. (**) | |
dataType | enum | ✔ | Data type of ctx.params. (***) |
error | object | Error object if not success. (*) | |
meta | object | ✔ | ctx.meta object. (*) |
stream | boolean | ✔ | Stream request. |
seq | int32 | Stream sequence number. |
(*) In case of schema-based serializers, the field value is encoded to JSON string.
(**) In case of schema-based serializers, the field value is encoded to JSON string and transferred as binary data.
(***) Used only in
ProtoBuf,Avro,Thriftor any other schema-based serializer to detect the original type of data.
Example with JSON serializer
{
"id": "a4dd3ae5-2eff-4924-94bc-4acb8ac034aa",
"meta": {},
"success": true,
"data": {
"message": "Hello Moleculer"
},
"ver": "4",
"sender": "nodeID-2"
}
EVENT
Topic name:
MOL.EVENT.node-1MOL.EVENTB.<group>.<event>(if built-in balancer is disabled)MOL-dev.EVENT.node-1(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
id | string | ✔ | Context ID. |
event | string | ✔ | Event name. E.g.: users.created |
data | object | Event payload. (**) | |
dataType | enum | ✔ | Data type of ctx.params. (***) |
meta | object | ✔ | ctx.meta object. (*) |
level | int32 | ✔ | Level of event. |
tracing | boolean | ✔ | Need to send tracing events. |
parentID | string | Parent context ID. | |
requestID | string | Request ID from ctx.requestID. | |
caller | string | Action/Event name of the caller. | |
stream | boolean | ✔ | Stream request. |
seq | int32 | Stream sequence number. | |
groups | Array<string> | Groups for balanced events. | |
broadcast | boolean | ✔ | Broadcast event |
(*) In case of schema-based serializers, the field value is encoded to JSON string.
(**) In case of schema-based serializers, the field value is encoded to JSON string and transferred as binary data.
(***) Used only in
ProtoBuf,Avro,Thriftor any other schema-based serializer to detect the original type of data.
Example with JSON serializer
{
"id": "e102630b-c702-4ff9-a0a1-52428395d57a",
"event": "some.test",
"data": {
"name": "John"
},
"groups": ["greeter"],
"broadcast": false,
"meta": {},
"level": 1,
"tracing": null,
"parentID": null,
"requestID": "e102630b-c702-4ff9-a0a1-52428395d57a",
"caller": null,
"needAck": null,
"ver": "4",
"sender": "nodeID-1"
}
EVENTACK
Not implemented yet.
Topic name:
MOL.EVENTACK.node-1MOL-dev.EVENTACK(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
id | string | ✔ | Event Context ID. |
success | boolean | ✔ | Is it successful? |
group | string | Group of event handler. | |
error | object | Error object if not success. (*) |
(*) In case of schema-based serializers, the field value is encoded to JSON string.
Example with JSON serializer
{
"ver": "4",
"sender": "node-100"
}
!!TODO!!
PING
Topic name:
MOL.PING(if broadcasts)MOL.PING.node-1(if sent only tonode-1)MOL-dev.PING(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
id | string | ✔ | Message ID. |
time | int64 | ✔ | Time of sent. (*) |
(*) The number of milliseconds between 1 January 1970 00:00:00 UTC and the given date.
Example with JSON serializer
{
"time": 1567677050576,
"id": "3e09738f-cedf-4985-85fe-344860c06cfd",
"ver": "4",
"sender": "nodeID-2"
}
PONG
Topic name:
MOL.PONG.node-1MOL-dev.PONG(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
id | string | ✔ | Message ID. |
time | int64 | ✔ | Timestamp of sent. (*) |
arrived | int64 | ✔ | Timestamp of arrived. (*) |
(*) The number of milliseconds between 1 January 1970 00:00:00 UTC and the given date.
Example with JSON serializer
{
"time": 1567677050576,
"id": "3e09738f-cedf-4985-85fe-344860c06cfd",
"arrived": 1567677050577,
"ver": "4",
"sender": "nodeID-1"
}
DISCONNECT
Topic name:
MOL.DISCONNECTMOL-dev.DISCONNECT(if namespace isdev)
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
ver | string | ✔ | Protocol version. |
sender | string | ✔ | Sender nodeID. |
Example with JSON serializer
{
"ver": "4",
"sender": "node-100"
}
Graceful starting & stopping
Start
The broker starts by establishing connection with the transporter (e.g., NATS server, MQTT broker). After successfully establishing the connection, it starts all services, i.e., calls service started handlers. Once all services have started successfully, broker publishes the local service list to remote nodes. Hence remote nodes will send requests only after all local service have started properly.
Start lifecycle sequence
Stop
When broker is stopping, it starts by publishing an empty service list to all remote nodes. This is done to inform all remote nodes that the node and it's service will be shut down. Next, broker starts stopping all local services. After that, the transporter disconnects.
Stop lifecycle sequence
Streams
While transferring streams, sequence number (seq) field is used to keep track of the chunks and their order. This is especially important in "multi-threaded systems" that can shuffle the packets before sending them. If packets arrive unordered they are stored in a pool until previous chucks arrive.
Disabled built-in balancer mode
When built-in load balancing mechanisms are disabled, the balancing is done by the brokers (e.g., NATS, RabbitMQ). This means that there are no local calls in actions and events. All requests are transferred to transporter that will be responsible for choosing the destination of the request and delivery of the message.
Changes from version 3
INFO
- added
instanceID: string - added
metadata: objectspecific to each nodes
PING / PONG
- added messaged ID
id: stringon both message types
REQUEST
paramsnow is not required anymore- removed
metricsproperty and addedtracing: booleanwhether tracing events is needed - new
paramsType: enumneeded for schema-based serializers - new
caller: stringproperty for action name of the caller - new
seq: int32to have better tracking of stream request
RESPONSE
- new
dataType: enumneeded for schema-based serializers - new
seq: int32to have better tracking of stream request
EVENT
- added context ID
id: stringin the payload - new
dataType: enumneeded for schema-based serializers - new
meta: objectthat representsctx.metaobject - added
level: int32as level of the event - added
tracing: booleanwhether tracing events is needed - new
caller: stringproperty for action/event name of the caller - added parent context ID
parentID: string - added
requestIDfrom context in the payload - handled stream requests, hence we have new properties
stream: booleanwhether it's a stream request andseq: int32to have better tracking