Based on the definition of PlainTalk, we now have a workable syntax for message and field framing. This document gives some recommendations for what to put in those messages.
Don't implement remote procedure calls (RPC). The difference between message passing and RPC is that operations in RPC are blocking while message passing allows multiple operations to be interleaved.
To support a request and response-style protocol with asynchronous message passing, use the first field of every request as an identifier. The response then uses the same identifier, allowing responses to arrive out of order and be understood in connection to the corresponding request:
0 define war 1 define freedom 1 ok freedom{1} is{1} slavery 0 ok war{1} is{1} peace
A good format for the responses is <message-id>
, then either ok
or error
. ok
can be followed by whatever makes sense as the response
for the request. error
can be more standardized, with for example one field
containing an error identifier (keep it human readable) and another field with a human readable
description of the error situation.
As an implementation concern it is worth noting that many protocols will likely handle messages mostly, or even completely, in order. It is then a good assumption that responses will arrive mostly in the same order as the requests were sent, making deque a good data structure for linking the responses to the correct request.
Do: Reserve an identifier to be used for server-initiated events, for example *
.
Let's pretend user "mag" is joining a chat room:
* join mag
With this basic structure, events can be sent by the server and handled by the client even when a long-running operation is in progress.
Consider having the "result" of a request arrive as server-initiated events as an alternative to
including everything in the ok
response. This can be easier for transmitting lists of
things, for example.
Start every connection with a protocol
request:
0 protocol chat reactions 0 ok chat stickers
In the above exchange we see that the client first declares his supported protocol features to be "chat" and "reactions". The server responds with its entire list of supported features, in this case "chat" and "stickers". The negotiated protocol after this exchange is the intersection of features in both lists, namely just "chat" in this case.
Since the server responds with its entire list of supported features regardless of what the client sends, we can save half a roundtrip time by having both the server and the client declare their protocol support up front:
* protocol chat stickers 0 protocol chat reactions 0 ok
Do: Negotiate features, not versions. It is easier to evolve the protocol to drop obsolete features this way. A feature in this list could include sets of available commands or maybe fixes to pre-existing features.