XArchived: Client server communication
How should the client and server communicate?
We could approach client-server in two broad ways:
- discrete requests (like REST)
- persistent connection (like WebSocket)
In the REST model, the server may have state but there isn't "connection state."
By using an always-open connection, clients and servers can immediately know when the other side dies. This allows the server to exit when there are no clients, and allows the client to restart the server as needed.
The always-open connection also allows an implementation of events, without resorting to polling.
Of course it's trivial to implement a bidirectional message stream with a raw socket. However, by using WebSocket we enable JavaScript clients.
- Requests: have a request ID to permit out-of-order replies, and a payload
- Replies: indicate the request ID they are the reply to, and have a payload
- Events: "unsolicited" messages (are not replied to), and have a payload
The request ID can simply be an incrementing serial so each thing that goes down the socket gets a number. If events also get a serial, it's an easy way to sort them by order of arrival.
It is probably useful in Scala to separate the envelope from the payload. The reason is that at the point of message creation, you don't know the envelope (for example, you would assign the request serial at the point of shoving the request down the socket, not at the point of request creation).
The envelope could also usefully include connection properties such as some identifier for the client, allowing the server to separate distinct clients.
type EnvelopeId
type PeerId
sealed trait Message
trait Request extends Message
trait Reply extends Message {
def replyTo: (PeerId, EnvelopeId)
}
trait Event extends Message
case class Envelope[M <: Message](id: EnvelopeId, peer: PeerId, message: M)
JSON is least-common-denominator, anything else is likely painful for non-Java clients.