Service to Service Communication
In a classic layered architecture, we haven’t really made a clear distinction between these terms. Everything is so close to each other that we don’t really have to worry about the semantic difference between Messages, Commands, Queries, Events. Before tackling these differences let’s see what they have in common! In the world of cloud computing, distributed systems rules! And in distributed systems, we have a lot of communication going on. Difference services have to talk through the network in order to accomplish their task. So, how they communicate? They exchange data through different means of communication. Each and one of the terms mentioned earlier is a means of communication.
Messages are the basic unit of communication, a means to transfer data from one system to another. You may be familiar with the term DTO (Data-Transfer Object); well, a message is nothing more than that. They have a well-defined structure allowing the services to talk to each other through an agreed interface. You can also think of a message as a blueprint. The message itself gives you the structure, but you have to fill in the blanks. Now, messages are only a supertype, meaning that messages can’t tell how the exchange of information will take place. Is there a direct communication between services? Or indirect, through a message broker? Will it always imply a response? Or we don’t even expect an acknowledgment of the message being received? These are all questions that the following message types will answer.
Most of the systems nowadays are built using the command pattern; REST API’s, RPC calls, etc. Commands are mainly used for 2 major use cases:
- send data to another system;
- ask another system to perform an action;
Commands are directed, meaning that the system initiating the command knows about the existence of the system performing the action. Let’s say that Application A wants to trigger a long-running job in Application B. App A can’t just randomly ask different systems “Hey, please run this job” because only Application B can do that. The same rule applies to sending data. You want to send order information to the “Order” application and not to the “Payment” one.
Commands represent actions that will happen in the future. The asking part takes place in the present but the action itself will happen in the future. That’s why commands are written in the present tense, e.g: placeOrder(). The only catch here is that the processing bit may happen immediately after, or sometime in the future.
Depending on the type of action that we want to trigger, we may receive a response or not. For example, if we would like to validate a UI form against the backend we would receive a yes or no response. Whereas, manually starting up a batch job may not include a response because it may take a very long time until it is completed.
Whenever you are sending data from one system to another you need ways to retrieve it as well. And so, queries were born. They are used to retrieve data from another system ( e.g: retrieving data from a database, interrogating a search engine, etc. ). You will notice that queries are extremely similar to commands but they are meant to be used for different purposes.
Application A needs “account” information in order to perform some action. Where do we get that data from? Well, we can, of course, ask every other application from the system if they have account-related information but queries are meant to be direct. Application A wants “account” information, then it will get it from the “account” data store. It can be either a REST API in front of a Database, a database table, or even a search engine. The point is that we know the exact system from where we need to receive our data from.
When we query a system for some information we need current data. We may, of course, request some historical data but the main point is that we need the current state of the data. Not past, not future but the current state of the data.
Always a Response
While submitting a query we will always receive a response containing the data that we have requested. Of course, sometimes we may receive an empty result but this still classifies as a response;
Events are, to some degree, in a league of their own. They are a different way of thinking compared to commands and queries, where everything is rather data-focused ( create, read, update, delete data ). Events are all about modeling actions that are being triggered throughout the system. So remember this, events are about actions, not data! If we look into when events make sense, we will find out that they are really good to model two use cases:
- notifications ( something has happened, or something has been triggered in the system );
- state change ( e.g: a user has updated his e-mail address, a light has been switched off, the cart has been updated, etc. );
Events just happen and they are the result of action so there is no target system. We can’t really say System A triggers events to System B. What we can say, is that System B is interested in the events produced by System A. However, not only System B may be interested in the events produced by System A, but also System C and D. Multiple interested parties can subscribe to the same event source without it knowing about them.
When we encounter an event, we refer to an action that already took place. That’s why, when we model events, we use the past tense ( e.g: order_placed, user_created, balance_debited, etc. ).
Since events are not directed to a specific application, the event producer does not expect any kind of response from its consumer. The application will produce those events and that’s it! It will not care about any kind of response.
Is it possible to have a “pure” solution?
By pure solution, I mean having a system in which all the interactions between services are represented by a single form ( commands, queries, or events). My answer to this question is NO! You will always find some sort of a hybrid between these 3 options. Of course, the system can be designed in such a way to promote a communication form as the main way of exchanging data ( Command-Driven Architecture, Event-Driven Architecture, etc. ) but it is impossible to map out all the features using a single form of communication. And that is fine! It is ok to have in an Event-Driven Architecture some synchronous REST calls that will create some data, or use Event Sourcing for a specific feature whereas all the other interactions in the system are Command-Driven.