This post is the first in a new series of blogs diving into the types of architectural decisions we've made at viax and the people and thought processes behind them. We wanted to put this information out for other technologists designing internet-based systems who might be asking similar questions, so it will be presented with less focus on viax and more through the lenses of general system design and what goes into our evaluations.One of the most important decisions when designing any system is deciding how to implement the interactions between components. Here we're referring the client-system interaction (or interaction between components) in a broad sense. This could be a web browser talking to a server through REST API or a function call inside of a program (in this case the code calling the function is a client and the function itself would be considered a system).Client-System InteractionConceptually there are two types of interactions that any client may perform with any system.1. Issue Commands"I obligate you – do this for me".Example commands:
-
- createProduct({...})
- fulfillOrder({...})
- sendEmail({...})
In this case, the client may decide not to respond on the result of command execution. If there is no response than the client is only able to deem the interaction with is complete once client is confident that the command was received/heard by the system.Usually the clients want to be notified with a result of a command execution, this where the second type comes into play.2. Subscribe to Known Message Channels"Notify me whenever this happens".Example subscription channels:
-
- createProduct_CommandExecutionResultByTid(tid)
- allCreatedProducts
- ordersFulfilledToday
- sentEmails
- systemNotifications
The “known” part (in the “Subscribe to known Message Channels”) means that there should be a way for the system to announce the list of all available subscription channels so that client can choose which ones to subscribe to. The way the list is announced is an implementation decision and could be an agreed naming convention, special endpoint or a separate subscription channel.Transport and ProtocolsThe two types of interactions above are conceptual and do not dictate the transports and protocols that should be used between a client and a system to exchange commands and messages.It is also important to note that there are two types of protocols here:
-
- The communication channel protocol
- The protocol (schema) of the message inside of the communication channel
Implementation example: REST APIIf we consider issuing a command through a REST API and receiving the result of a command execution, you'll notice that HTTP over TCP/IP here is your single command communication channel (your transport layer). Client automatically and implicitly subscribes to the result channel and waits for response message notification (usually in a synchronous, blocking way).
The client doesn't close the socket immediately after sending a command, it waits for a message from the system containing a result of command execution. The manner in which it is returned would be an implementation decision for the command+subscribe interaction. If HTTP over TCP/IP is the communication channel (transport layer), HTTP endpoint name is the command to be executed then subscription channel is implicitly derived from the command communication channel and JSON is the message communication protocol.If you look at the different stages and read how client is talking to the system you may see some particular qualities of this type of communication:
-
- Client relies on the TCP protocol to have command delivery guarantee. The TCP protocol relies on the Ethernet layer and assumes that the physical network cable is plugged into the client computer.
- Client has to know the address of the system beforehand to be able to establish a communication channel.
- If the system at the client-known address is down (overloaded with requests, crashed, restarting, upgrading, migrating) client won’t be able to establish the command communication channel. Now it’s the client responsibility to decide what to do (e.g. manage retries).
- The established communication channel is the only channel that could be used to send messages back to the client. And the concrete instance of system process (a running program/service) is the only one who can send messages back to the channel. It is not possible for other processes to attach to that channel and send their messages to the client. You can’t have one service accepting command request and another service sending command response message back. You can’t decouple receiver from the responder at the protocol level. It is not possible due to the nature of this specific communication channel protocol.
- The communication channel is being created and destroyed every time when a new command instance is issued & response received. No channel reuse. The connection is being held all the time while the command is being processed. If there are a lot of commands executed concurrently it means that system has a lot of connections open at the same time.
- Client takes responsibility on it’s side to wait and keep the communication channel open for reasonable (for the client) amount of time. If system does not send a message back for too long (from the client's point of view) client may decide to close the channel and will never be notified of the result of command execution. Once the channel is closed there is no way for the client to later get the result of command execution.
Other Possible ImplementationsREST API is not the only way to implement client-system interaction. For example client could issue commands through one channel using Protocol A and subscribe to a different message channel using a Protocol B.Example command channels:
-
- Pulsar topic: "requests-createProduct", avro message with request data + tid. Avro schema is a protocol, pulsar topic is a transport.
- HTTP /createProduct endpoint (returning only tid).
- GraphQL createProduct() mutation over http (returning only tid).
- GraphQL createProduct() mutation using WebSocket as a transport.
Example notification channels:
-
- Pulsar topic: "responses-createProduct".
- Periodical pulling of HTTP /createProductResponse/{tid} endpoint.
- Websocket connection to /createProductResponse/{tid} endpoint.
- GraphQL subscription “responses-createProduct”.
Up Next
Next we will continue the conversation on client-system interactions and move into architectural designs based on Apache Pulsar and Datomic.