Communication Structures in Microservices Explained

Communication Structures in Microservices Explained

RPC (Remote Procedure Call)

A remote procedure call is a communication protocol between a client and a server. The client asks the server to do a particular operation. The server performs that operation immediately and returns some information back to the client (request-response pattern). This communication often happens over the Internet. We can classify most of the HTTP traffic, SQL queries, and SOAP as RPCs.

There are several known RPC technologies such as; gRPC (an open-source RPC framework developed by Google), JSON-RPC (uses JSON as data format), XML-RPC (uses XML as data interchange format), Microsoft .NET Remoting (enables RPC communication between .NET applications).

Even though some resources group RPCs as synchronous and asynchronous, in the industry, the common understanding when mentioning RPC is synchronous communication.

Within the context of RPCs, the terms stateful and stateless refer to the management of context between the client and the server during the RPC process. To be more precise, in a stateless procedure call, the server accepts data without relying on previous interactions or some pre-stored context, performs some operations, and returns some data back to the client. The server doesn't store any operational data. A service that creates a report in PDF format with given values can be a good example of a stateless procedure call. On the flip side, if the server keeps track of client data based on past service interactions, sessions, and requests, this is called a stateful procedure call. While stateless procedures are simple and easy to implement, test, and maintain, stateful procedures bring some additional data management work. State management in distributed systems is an important topic to always keep in mind and consider when planning microservices architectures that contain RPC communications.

Synchronous Communication

In a microservices architecture, synchronous communication refers to a communication pattern where a service makes a request to another service and waits for a response before continuing its execution which exactly means an RPC-style communication.

Synchronous communication between services is very straightforward to implement and test with various known API development languages and tools. On the other hand, if a service is slow, it can cause delays and failures in all dependent services, which can also create cascading failures. There is also a coupling risk by defining hardcoded service addresses instead of using service discovery tools (DNS, Apache Zookeeper, etc.) when working with synchronously communicating services. Coupling in microservices architecture can lead to unwanted scalability issues in the future.

A client service calling another service also means that the client has a dependency on another service. Dependency in synchronous communication requires thinking about both success and failure scenarios. Let's say that we have a chat service and a notification service in our microservices environment. The chat service makes calls to the notification service when it receives a new chat message. The notification service performs the notification operations and notifies the chat service about the success or failure of the notification process. Then the chat service takes action based on the response that comes from the notification service. If a network failure occurs during the notification operation, even if the notification process is successful, because the notification service is not able to reach the chat service, the chat service comprehends this as a failure and doesn't complete the messaging process properly. This and similar unhappy paths should be considered while planning microservices communications, and operational states must be defined accordingly. This type of connected stateful operation across multiple independent services is called a distributed transaction, and there are special patterns used for coordination in the industry (eq; SAGA, Routing Slip, etc.).

Asynchronous Communication

In a microservices architecture, when a service sends a message or an event to another service without waiting for an immediate response, it's called asynchronous communication. In this communication pattern, the client becomes a sender or producer and the server becomes a receiver or consumer. Instead of establishing synchronous communication, the sender and receiver communicate through a message bus, message broker, or queue.

While RPC (as a synchronous communication), the communication is two-way, client to server and server back to the client. With a message bus (as an example of an asynchronous communication technique), the communication is one-way, the sender doesn't wait for a response from a consumer.

Asynchronous communication models are easy to scale by adding and removing consumers and distributing the workload. Messages are temporarily stored in message brokers and processed later, making the system more resilient by reducing the risk of data loss. This approach aligns well with event-driven architecture where services react to events/messages and take action (do an operation, update state, etc.) accordingly.

On the other hand, asynchronous communication can introduce complexity to the system as message brokers and queues need to be maintained. Compared to synchronous communication, monitoring, and debugging come with more complexity when using asynchronous techniques.

A microservices system can be hybrid in terms of communication methods. It can contain both synchronous and asynchronous service communications. It's important to consider all aspects such as monitoring, debugging, performance, state management, security, project budget, and organizational maintainability capacity (team capabilities) while making decisions about the communication types.