Microservices
Reactive microservices: Part 4 - why asynchronous communication matters
Messaging instead of invocation
If your experience is mainly with enterprise applications, working with a system of microservices will require a change in the way you think about communication. The WAR and JAR monolithic deployments of the past allowed you to make assumptions about the availability of objects. In most cases, you were able to treat requests and responses as if they were simply local method invocations. While simple, this pattern obscures the network and pretends that it is reliable.
In distributed systems, you have no assurances that the service you want to invoke will be running, whether a network issue will prevent your request from arriving, or whether the response will ever come. The inherent dynamic nature of distributed systems makes it important to deal with communication failures as normal occurrences. That’s why it makes sense to use messages and realize how they differ from remote method invocation.
Messaging does not need to be point-to-point. Use of messaging can, and often does, mean adopting an event-driven architecture, which can bring additional benefits. Event-driven systems promote autonomy and decoupling, allowing the development organization and the resulting system to scale more easily. They provide good options for managing consistency and persistence.
Messages offer a real-world model that allows you to reason logically about requirements. For example, if one of your workmates is away from their desk and you have a question for them, you could leave a note. You don’t know when they might respond: they could be on vacation or even have left the company. This leaves you with a limited number of options:
- For an immediate response, you might find someone else who is available to answer.
- If the response is necessary — but not time-sensitive — you might tape the note to the desk to make sure it doesn’t get lost and try again if you don’t get a response.
- If the message has value for a limited time and is not critical (such as a lunch invitation, which has no value once the lunch is over) you might just leave the note and forget it.
The desired outcome determines how you handle the message. Effectively, you need to choose between synchronous and asynchronous messaging. In synchronous messaging, a requestor passes a message to another service and expects a timely response, so the requestor waits. This is the familiar pattern often seen in HTTP calls between a client and server.
In contrast, with asynchronous messaging, the requestor simply sends a message and continues with its business. Since microservices depend on the health of their host and network connections, asynchronous messaging offers an obvious advantage. The illustration below illustrates how processing requests asynchronously can speed up execution.
If the message is important, you need some way of persisting it to make sure it will be dealt with at some point in time. An event-driven architecture offers several ways of handling this. For example, in a microservices system, you could use a message broker with delivery guarantees, or write such messages to a database or log. If a reply is required, the sender could just wait for an acknowledgment that the request was received and continue its work, expecting the answer to the question later.
Remembering the events in the system, and allowing for eventual consistency will help you choose the right type of messaging for the job.
Asynchronous message-passing helps make the constraints—in particular the failure scenarios—of network programming first-class, instead of hiding them behind a leaky abstraction and pretending that they don’t exist…