Couple of Minutes: Serverless Microservices Communication
In the previous post, we discussed about how we have decomposed our serverless application into microservices. In this post, we will look at different communication patterns used for inter-service and intra-service (between functions within same microservice) communication.
There are 2 styles of communication broadly in use —
Synchronous Communication and Asynchronous Communication
The picture below depicts the communication styles used in our application —
In Asynchronous mode of communication, the service that posts the request doesn’t wait for immediate response. The thread gets closed as soon as the request is posted. In such cases, it doesn’t make sense to call them request messages. They are plain messages. That’s the reason why messaging is used synonymous with asynchronous communication. This helps decouple message sender from message receiver.
In our example, we have 2 types of asynchronous communication — Event driven notification and messaging via queue.
Event driven notification is used in our case to notify object creation and deletion in S3 bucket. These events in turn invoke respective Lambda functions.
Messaging via queue is used by extractMetadata function (which fetches the metadata from Rekognition) to post the extracted metadata to a SQS queue. addMetadata function consumes the message from the SQS queue and adds to database. The SQS queue here has helped decouple extractMetadata and addMetadata.
In Synchronous mode of communication, the service that sends the request waits for a response. This mode of communication is often used when the service depends on the response message for further processing. User Interfaces are easier examples that often use this mode of communication.
In our application, we have used API gateway to make synchronous calls between user interfaces and our Lambda functions (also between Lambda functions where synchronous request-response communication is used).
It is always advised to use this mode of communication cautiously because it has cascading effect. Let us pick one example from our application.
In the example above, getDigiAds function makes 2 synchronous REST calls one after the other sequentially — a Rest call to the other lambda function (getMetadata) via API Gateway and a Rest call to 3rd party Deals API. The sequence diagram below depicts the flow -
Remember, Lambda functions are billed for their execution time. getDigiAds execution waits for the response from getMetadata and Deals API. So getDigiAds is billed waiting for getMetdata function’s execution and response from Deals API. If Deals API is down/fails, the client wouldn’t get any deals to render and the execution time of getDigiAds and getMetadata is spent for waste. The cascading effect in terms of latency, failure and cost is apparent from above sequence diagram. This appears to be a poor choice for communication for this use case. We may have to evaluate alternative mode of communications (web sockets probably).
A lot of care must be taken while choosing communication modes. Go with horses for courses. The style that best fits the use case must be chosen. While creating the architecture diagram in the first post, this problem with synchronous communication wasn’t apparent. When we applied color coding to the communication style and sequenced the flow with a diagram, this cascading effect started appearing apparent. So these are good tools to evaluate/review architecture upfront.