By the end of the lesson, you can design the message envelope of a WebSocket connection, distinguish a request, a response and an event, and list what identifiers and acknowledgements are needed for reliable operation.
Why is it needed?
send(JSON.stringify(data)) is only the technique of transmission; the receiver still does not know what operation to perform, and the sender does not know whether it succeeded. WebSocket does not define what a "login" or a "subscription" is (10.1). A working system must establish message types, responses, errors and ordering rules, that is, an application protocol.
Message envelope
A consistent envelope, giving the same fields for every message, is useful:
version: the version of the message format (not the version of the business state);
type: the meaning of the message;
requestId: for pairing a request and its response;
eventId: the unique identifier of a server-side fact;
payload: the actual content.
The envelope can also be named by the subprotocol negotiated in the WebSocket handshake (10.1). The message identifier, the business operation identifier (idempotency key, 6.5) and the trace identifier (10.5) must be kept apart; none of them replaces another.
Request, response, event
There are three categories: a request initiated by the client, the result given to it, and an event initiated by the server. The type field distinguishes them. Accepting a subscription is not the same as delivering the earlier events; the contract can mark separately the end of the replay and the start of the live stream. If the start is from a state, the common version boundary of the snapshot and the events that follow must be defined (10.3).
Errors and invalid messages
For an unknown type, bad JSON, a message that is too large and a forbidden operation, a stable error code is needed. Not every error justifies closing the connection: a bad request can get a reply, but with repeated violations or protocol incompatibility closing may be justified. The server validates the schema too; client-side validation and the type system do not prove the correctness of incoming data, and a user identifier is not treated as authentic merely because it arrived as a JSON field.
Meaning of acknowledgements
"It arrived", "it was accepted" and "it was executed" are different acknowledgements (8.2). For a long operation the server may first give a task identifier and send the result later. If the operation has lasting business significance, the client must be able to query the state by another route after losing the connection. If the client does not receive a final result, the repetition must relate to the same logical operation: a new requestId alone may look like a new command; a separate idempotency key or a documented reuse rule is needed.
Ordering and parallel processing
On one connection the order of frames is preserved (the Socket.IO documentation also distinguishes that it gives the guarantee of this order independently of the transport), but operations the server starts in separate tasks can finish in a different order. Responses are therefore paired by identifier, not by "the first response belongs to the first request". Between several connections or server instances a separate ordering contract is needed (8.3). The message version and the entity version mean different things: the first marks the format, the second the progress of the business state.
Characteristics
Aspect
WebSocket messaging
Direction
Two-way
Initiator
Both parties
Connection persistence
Persistent
Latency
Low; processing depends on the server-side work
Load
The number of messages and the cost of server-side work
Fault tolerance
Identifiers, acknowledgements, idempotency are part of the contract
When it fits
When request–response and events are needed on the same channel with low latency
When it does not
When a durable business result would be endangered by the channel breaking and there is no query route
Common misconceptions
Statement
Clarification
"A JSON message is enough as a protocol."
Type, identifier, error and acknowledgement rules are also needed.
"On a connection the first response belongs to the first request."
Operations may end in a different order; pair by identifier.
"A new requestId = the same operation."
It may look like a new command; an idempotency key is needed.
"Client validation is enough."
The server must check too.
In short
A WebSocket needs an application protocol: type, identifiers, error codes, acknowledgements.
The envelope: version, type, requestId/eventId, payload.
"Arrived", "accepted" and "executed" are separate acknowledgements.
Pair responses by identifier; a durable operation should have a queryable state.
Review questions
Why is JSON.stringify not enough as a protocol?
How does the message version differ from the entity version?
How should a request be repeated if no final result arrived?
An Application Protocol over WebSocket
Objectives
By the end of the lesson, you can design the message envelope of a WebSocket connection, distinguish a request, a response and an event, and list what identifiers and acknowledgements are needed for reliable operation.
Why is it needed?
send(JSON.stringify(data))is only the technique of transmission; the receiver still does not know what operation to perform, and the sender does not know whether it succeeded. WebSocket does not define what a "login" or a "subscription" is (10.1). A working system must establish message types, responses, errors and ordering rules, that is, an application protocol.Message envelope
A consistent envelope, giving the same fields for every message, is useful:
{ "version": 1, "type": "subscribe", "requestId": "req-31", "payload": { "channel": "orders", "afterEventId": "evt-90" } }version: the version of the message format (not the version of the business state);type: the meaning of the message;requestId: for pairing a request and its response;eventId: the unique identifier of a server-side fact;payload: the actual content.The envelope can also be named by the subprotocol negotiated in the WebSocket handshake (10.1). The message identifier, the business operation identifier (idempotency key, 6.5) and the trace identifier (10.5) must be kept apart; none of them replaces another.
Request, response, event
There are three categories: a request initiated by the client, the result given to it, and an event initiated by the server. The type field distinguishes them. Accepting a subscription is not the same as delivering the earlier events; the contract can mark separately the end of the replay and the start of the live stream. If the start is from a state, the common version boundary of the snapshot and the events that follow must be defined (10.3).
Errors and invalid messages
For an unknown type, bad JSON, a message that is too large and a forbidden operation, a stable error code is needed. Not every error justifies closing the connection: a bad request can get a reply, but with repeated violations or protocol incompatibility closing may be justified. The server validates the schema too; client-side validation and the type system do not prove the correctness of incoming data, and a user identifier is not treated as authentic merely because it arrived as a JSON field.
Meaning of acknowledgements
"It arrived", "it was accepted" and "it was executed" are different acknowledgements (8.2). For a long operation the server may first give a task identifier and send the result later. If the operation has lasting business significance, the client must be able to query the state by another route after losing the connection. If the client does not receive a final result, the repetition must relate to the same logical operation: a new
requestIdalone may look like a new command; a separate idempotency key or a documented reuse rule is needed.Ordering and parallel processing
On one connection the order of frames is preserved (the Socket.IO documentation also distinguishes that it gives the guarantee of this order independently of the transport), but operations the server starts in separate tasks can finish in a different order. Responses are therefore paired by identifier, not by "the first response belongs to the first request". Between several connections or server instances a separate ordering contract is needed (8.3). The message version and the entity version mean different things: the first marks the format, the second the progress of the business state.
Characteristics
Common misconceptions
In short
version,type,requestId/eventId,payload.Review questions
JSON.stringifynot enough as a protocol?Learning resources