This is an article from DZone’s 2023 Software Integration Trend Report.
A microservices architecture is an established pattern for building a complex system that consists of loosely coupled modules. It is one of the most talked-about software architecture trends in the last few years. It seems to be a surprisingly simple idea to break a large, interdependent system into many small, lightweight modules that can make software management easier.
Here’s the catch: After you have broken down your monolith application into small modules, how are you supposed to connect them together in a meaningful way? Unfortunately, there is no single right answer to this question, but as is so often the case, there are a few approaches that depend on the application and the individual use case.
Two common protocols used in microservices are HTTP request/response with resource APIs and lightweight asynchronous messaging when communicating updates across several microservices. Let’s explore these protocols.
Types of Communication
Microservices can communicate through many different modes of communication, each one targeting a different use case. These types of communications can be primarily classified in two dimensions. The first dimension defines if the communication protocol is synchronous or asynchronous:
|SYNCHRONOUS vs. ASYNCHRONOUS COMMUNICATION|
|Communication pattern||The client sends a request and waits for a response from the server.||Communication is not in sync, which means it does not happen in real time.|
|Coupling||The client code can only continue its task further when it receives the server response.||In the context of distributed messaging, coupling implies that request processing will occur at an arbitrary point in time.|
|Failure isolation||It requires the downstream server to be available or the request fails.||If the consumer fails, the sender can still send messages. The messages will be picked up when the consumer recovers.|
The second dimension defines if the communication has a single receiver or multiple receivers:
COMMUNICATION VIA SINGLE vs. MULTIPLE RECEIVERS
|Single Receiver||Multiple Receivers|
|Communication pattern||It implies that there is point-to-point communication that delivers a message to exactly one consumer that is reading from the channel, and that the message is processed only once.||Communication from the sender is available to multiple receivers.|
|Example||It is well-suited for sending asynchronous commands from one microservice to another.||The publish/subscribe mechanism is where a publisher publishes a message to a channel and the channel can be subscribed by multiple subscribers/receivers to receive the message asynchronously.|
The most common type of communication between microservices is single-receiver communication with a synchronous protocol like HTTP/HTTPS when invoking a REST API. Microservices typically use messaging protocols for asynchronous communication between microservices. This asynchronous communication may involve a single receiver or multiple receivers depending on the application’s needs.
Representational State Transfer
Representational state transfer (REST) is a popular architectural style for request and response communication, and it can serve as a good example for the synchronous communication type. This is based on the HTTP protocol, embracing verbs such as GET, POST, PUT, DELETE, etc. In this communication pattern, the caller waits for a response from the server.
Figure 1: REST API-based communication
REST is the most commonly used architectural style for communication between services, but heavy reliance on this type of communication has some negative consequences when it comes to a microservices architecture:
- Multiple round trips (latency) – The client often needs to execute multiple trips to the server to fetch all the data the client requires. Each endpoint specifies a fixed amount of data, and in many cases, that data is only a subset of what a client needs to populate their page.
- Blocking – When invoking a REST API, the client is blocked and is waiting for a server response. This may hurt application performance if the application thread is processing other concurrent requests.
- Tight coupling – The client and server need to know about each other. It increases complexity over time and reduces portability.
Messaging is widely used in a microservices architecture, which follows the asynchronous protocol. In this pattern, a service sends a message without waiting for a response, and one or more services process the message asynchronously. Asynchronous messaging provides many benefits but also brings challenges such as idempotency, message ordering, poison message handling, and complexity of message broker, which must be highly available.
It is important to note the difference between asynchronous I/O and the asynchronous protocol. Asynchronous I/O means that the calling thread is not blocked while the I/O operations are executed. This is an implementation detail in terms of the software design. The asynchronous protocol means the sender does not wait for a response.
Figure 2: Messaging-based communication
Asynchronous messaging has some advantages over synchronous messaging:
- Loose coupling – The message producer does not need to know about the consumer(s).
- Multiple subscribers – Using a publisher/subscriber (pub/sub) model, multiple consumers can subscribe to receive events.
- Resiliency or failure isolation – If the consumer fails, the producer can still send messages. The messages will be picked up when the consumer recovers from failure. This is especially useful in a microservices architecture because each microservice has its own lifecycle.
- Non-blocking – The producers and consumers can send and process messages at their own pace.
Though asynchronous messaging has many advantages, it comes with some tradeoffs:
- Tight coupling with the messaging infrastructure – Using a particular vendor/messaging infrastructure may cause tight coupling with that infrastructure. It may become difficult to switch to another vendor/messaging infrastructure later.
- Complexity – Handling asynchronous messaging may not be as easy as designing a REST API. Duplicate messages must be handled by de-duplicating or making the operations idempotent. It is hard to implement request-response semantics using asynchronous messaging. To send a response, another queue and a way to correlate request and response messages are both needed. Debugging can also be difficult as it is hard to identify which request in Service A caused the wrong behavior in Service B.
Asynchronous messaging has matured into a number of messaging patterns. These patterns apply to scenarios when several parts of a distributed system must communicate with one another in a dependable and scalable way. Let’s take a look at some of these patterns.
The pub/sub pattern implies that a publisher sends a message to a channel on a message broker. One or more subscribers subscribe to the channel and receive messages from the channel in an asynchronous manner. This pattern is useful when a microservice needs to broadcast information to a significant number of consumers.
Figure 3: Pub/sub pattern
The pub/sub pattern has the following advantages:
- It decouples publishers and subscribers that need to communicate. Publishers and subscribers can be managed independently, and messages can be managed even if one or more subscribers are offline.
- It increases scalability and improves responsiveness of the publisher. The publisher can quickly publish a message to the input channel, then return to its core processing responsibilities. The messaging infrastructure is responsible for ensuring messages are delivered to interested subscribers.
- It provides separation of concerns for microservices. Each microservice can focus on its core responsibilities, while the message broker handles everything required to reliably route messages to multiple subscribers.
There are a few disadvantages of using this pattern:
- The pub/sub pattern introduces high semantic coupling in the messages passed by the publishers to the subscribers. Once the structure of the data is established, it is often difficult to change. To change the message structure, all subscribers must be altered to accept the changed format. This can be difficult or impossible if the subscribers are external.
- Another drawback of the pub/sub pattern is that it is difficult to gauge the health of subscribers. The publisher does not have knowledge of the health status of the systems listening to the messages.
- As a pub/sub system scales, the broker often becomes a bottleneck for message flow. Load surges can slow down the pub/sub system, and subscribers can get a spike in response time.
In the queue-based pattern, a sender posts a message to a queue containing the data required by the receiver. The queue acts as a buffer, storing the message until it is retrieved by the receiver. The receiver retrieves messages from the queue and processes them at its own pace. This pattern is useful for any application that uses services that are subject to overloading.
Figure 4: Queue-based pattern
The queue-based pattern has the following advantages:
- It can help maximize scalability because both the number of queues and the number of services can be scaled to meet demand.
- It can help maximize availability. Delays arising in the producer or consumer won’t have an immediate or direct impact on the services, which can continue to post messages to the queue even when the consumer isn’t available or is under heavy load to process messages.
There are some disadvantages of using this pattern:
- When a consumer receives a message from the queue, the message is no longer available in the queue. If a consumer fails to process the message, the message is lost and may need a rollback in the consumer.
- Message queues do not come out of the box. We need to create, configure, and monitor them. It can cause operational complexity when systems are scaled up.
Keys To Streamlined Messaging Infrastructure
Asynchronous communication is usually managed through a message broker. There are some factors to consider when choosing the right messaging infrastructure for asynchronous communication:
- Scalability – the ability to scale automatically when there is a load surge on the message broker
- Data persistency – the ability to recover messages in case of reboot/failure
- Consumer capability – whether the broker can manage one-to-one and/or one-to-many consumers
- Monitoring – whether monitoring capabilities are available
- Push and pull queue – the ability to handle push and pull delivery by message queues
- Security – proper authentication and authorization for messaging queues and topics
- Automatic failover – the ability to connect to a failover broker automatically when one broker fails without impacting publisher/consumer
More and more, microservices are becoming the de facto approach for designing scalable and resilient systems. There is no single approach for all communications between microservices. While RESTful APIs provide a request-response model to communicate between services, asynchronous messaging offers a more scalable producer-consumer relationship between different services. And although microservices can communicate with each other via both messaging and REST APIs, messaging architectures are ideal for improving agility and moving quickly. They are commonly found in modern applications that use microservices or any application that has decoupled components.
When it comes to choosing a right style of communication for your microservices, be sure to match the needs of the consumer with one or more communication types to offer a robust interface for your services.
This is an article from DZone’s 2023 Software Integration Trend Report.
Leave a Reply