We discussed in the previous post that intra-microservice communication should not be triggered using nested requests. Such approach can lead to a complex tree of blocking calls thereby degrading the request latency.
Updates should not be requested immediately but rather be pushed whenever state changes happen. This kind of communication should be asynchronous to be able to achieve better performance. A synchronous call that places the Update request in a queue and immediately returns an “accepted” result instead of waiting for the actual update to be performed helps achieve low latency and high throughput. Since the requests complete faster, threads free up faster and more threads remain available to accept new requests.
Publisher/Subscriber aka Pub/Sub pattern can be used to achieve asynchronous communication between microservices.
In Pub/Sub pattern, the publishers and subscribers are not known to each other. They need not know each other. Communication happens by publishing messages to a Queue. The Subscribers listen to the Queue for messages of their interest. Since the sender of the message and listener of the message do not talk to each other but to a broker that manages the Queue, it increases microservice independence. Here the broker takes care of queueing, dequeuing and distributing the messages to Subscribers.
Http Polling pattern is another pattern that can be used to achieve asynchronous communication between presentation/client side microservice and backend microservices. Polling is useful to client side code as it can be hard to use long running connections.
Client application makes a synchronous call to a backend microservice – typically an API, triggering a long running operation on the backend. The API responds synchronously as quick as possible with “accepted” status and a reference endpoint – status endpoint that the client can poll to check for the result of the operation.
The API queues the request to a Queue for further processing. While the work is pending, the status endpoint when polled returns HTTP 202 status. Once the work is completed, the status endpoint when polled returns the result.
Eventual Consistency pattern can be used to achieve data independence. When a microservices needs data that’s originally owned by other microservices, instead of making asynchronous requests to other microservices to fetch the required data, each microservice should store a copy of data that it requires.
When a microservice that owns the data, updates the state data it manages, it should notify other microservices so that they can update their copy of data. This pattern is called Eventual Consistency because when a data changes in one microservice, other microservice eventually sync the state of their copy of data in a disconnected manner.
Duplicating data across multiple microservices is NOT an incorrect design. It instead allows translating the data into terms specific to each bounded content. For example, an application may have “Identity-API” that is responsible for managing user’s data with an entity named “User”. However, when the “Ordering” microservice wants to store user’s information, it will want it as a different entity called “Customer”. The Customer entity shares the same identity with the original User entity, but it might have only a few attributes needed by the Ordering domain.
A communication mechanism is required to communicate the updates happening between microservices boundaries. This is achieved using Integration Events using a message broker or HTTP polling. The approach depends on the domain complexity and the desired scalability of the microservice.
The patterns discussed above are best practices for implementing loosely coupled, highly autonomous and performant microservices. There are other communication patterns such as Request/Response pattern, Service aggregator pattern, Service mesh pattern etc. However, they all are fundamentally a form of synchronous or asynchronous communication patterns and are used for organizing the communication and the handling cross cutting concerns involved in it.
In the next post, we will discuss the tools and technologies needed for implementing asynchronous communication while developing microservices. Our choice of platform will be .NET Core 5.0 and Azure Cloud Services. In future, we will also cover patterns for implementing Resiliency. But before that, lets get into code and implement a simple microservice that incorporates the above aspects of loose coupling and data independence.