For a microservices architecture to operate smoothly, the services and components that populate the architecture must communicate reliably and consistently.
Unfortunately, each call and response between these entities adds latency, and therefore it's important to keep individual calls to a minimum. One way to fix this extra chatter is to optimize code so that it cuts down on unnecessary communications between services. However, development teams must still optimize the necessary communication that remains.
Let's review the basic types of microservices communication, and review the pros and cons of commonly used protocols and methods.
Synchronous vs. asynchronous communication
There are two basic forms of microservice communication: synchronous and asynchronous. In synchronous communication, calls create a chain of dependencies through all the downstream services. For example, a service that makes a synchronous call to a shopping cart may also find itself dependent on the associated payment and warehousing services. If one or more of these downstream services are unavailable, it's unlikely that the caller will get a response. Even if there is a response, it may take so long that the user will end the session.
The way around this problem is to use asynchronous communications. Rather than building a continuous chain of dependencies, the asynchronous approach handles each communication separately. This lets each microservice operate independently and react to calls without waiting on a response from the downstream services.
For example, the requesting service makes a call to the shopping cart, which can immediately indicate that it is updated and ready for the customer to view. Meanwhile, the shopping cart can make downstream calls to warehousing, payment and so on. These responses can be stored and ready for use when the customer clicks through to complete the transaction.
Given these examples, it may seem that asynchronous is always the better approach, but that's not necessarily the case. For instance, certain real-time query calls may need to be synchronous, especially when the response received significantly alters the calling services function. However, you can still make most query calls asynchronously.
HTTP, REST and AMQP
HTTP is a common, inter-microservice communication protocol most often used for synchronous communication. Its brokerless approach facilitates direct interactions between all microservices. While HTTP provides simple communication, it can easily cause unwanted dependencies. It's recommended to use HTTP only as the primary protocol between a user interface and the highest level of services. Beyond this, asynchronous methods are recommended.
However, introducing REST to HTTP can help handle request/response communications asynchronously. If you need to use HTTP, consider using REST as well, because there is much standardization between the two. The major benefit to using the combined protocols is faster response times.
For those who choose to stick with asynchronous protocols, consider exploring the advanced message queuing protocol (AMQP). This widely available and mature protocol provides a standard method for microservices communication and should be a priority for those developing truly composite microservices apps.
Asynchronous protocols like AMQP use a lightweight service bus similar to a service-oriented architecture (SOA) bus, though much less complex. Unlike HTTP, this bus provides a message broker that acts as an intermediary between the individual microservices, thus avoiding the problems associated with a brokerless approach. Keep in mind, however, that a message broker will introduce extra steps that can add latency. The individual services still contain their functional and operational logic, and will need time to process that logic. The bus simply helps standardize and throttle those communications.
Major cloud platforms, such as Azure, provide their own proprietary service bus for message brokering. However, there are also third-party options such as RabbitMQ, an open source message broker based in the Erlang programming language.
The single- and multiple-receiver models
The way responding services connect and react to calls is also something to consider. Here it's important to think about single- and multiple-receiver models.
In a single-receiver model, the calling service creates a message-based request that connects with a single responding service, facilitating a direct, one-to-one response. Again, this optimizes response times, but does not protect against cascading failure. For instance, if the responding microservice is designed to self-recover and retry following a failure, the requesting microservice may receive repeated responses to its call without realizing a failure has occurred.
Alternatively, the multiple-receiver model is based on a publish-and-subscribe approach. The requesting service transmits a call to any other services that might provide a response. These messages are queued and managed on a first-come, first-served basis, delivering the response to the calling service when ready. This provides much greater service availability, optimizes response times and provides a greater means of adding resources when they are required. For example, a new service instance can be spun up and introduced to the overall environment, automatically adjusting the upstream services to connect.
Back-end system developers should explore asynchronous, message-brokered approaches that provide standard communication. It's fine to use synchronous communication between a simple front- and back-end to provide a better user experience. However, with complex operations, asynchronous communication works best.