DEV Community

Cover image for Asynchronous Processing with message queues
Prateek
Prateek

Posted on

Asynchronous Processing with message queues

Ever heard of fire and forget processing? If not, a short and simple scenario which can help us understand this is where a caller returns immediately upon invocation, while the actual execution of callee is being executed parallelly. In a nutshell, caller is not blocked for the task to be finished after initiating callee's execution. Let's see a real-life example to understand it better.

class RequestController {
  @PostMapping("/order")
  fun placeOrder(): Response {
    OrderService().placeOrder()
    MailService().sendMail()
    return Response.SUCCESS
  }  
}
class MailService {
  fun sendMail() {
    Thread {
      // mail sending logic
    }.start()
  }
}

In above kotlin snippet, we can see RequestController upon receiving the request does two things:

  1. Place an actual order by using OrderService.
  2. Send a mail notification using MailService.

If we again focus on MailService code, it sends a mail in a new Thread. By using a new Thread, MailService is ensuring that it's caller isn't blocked for it to finish. Since sending a mail could be a heavy IO operation (establishing a connection with SMTP server which is prone to network errors), Hence Client shouldn't wait for the server to send mail which can be executed in an Asynchronous manner.

Apparently, if an order is placed but mail sending logic fails because of any error. Client would still receive a success response indicating the status of an order placed. Since mail notification is a way to notify the user regarding the order placed, it's failure shouldn't be a snag in the user's main journey.

It's kind of Notification to MailService to initiate its execution which is achieved by calling a function of MailService. This kind of communication is required where time-consuming processing needs to be done, such as processing large volumes of data but no immediate result is sought by the caller.

Now we all know what asynchronous procedures are and how they can be effectively leveraged in an application process, but What about multiple processes? such as Microservices !!

In a microservices architecture, each service is designed as a self-sufficient, independent piece of software. In such an architecture, a single-use case often consists of multiple HTTP calls to multiple services. HTTP calls are the synchronous way of microservices to communicate with each other because requester expects an immediate response. Similarly, we want our services to work in an asynchronous manner too. But wait, we can't call a function which is running as part of a different process (might be on a different machine). This is where message-queue comes to rescue us.

Message Queues

Message queues provide an asynchronous way of communication to multiple application processes. A process that passes the message to other process is called a Producer and the system receives the message is known as Consumer. Producer puts a message on the message-queue and does not require an immediate response and continues its own processing.

Our last Email example is a perfect candidate to understand this asynchronous messaging system.

Message broker email example

When order service receives an order request from the client, it notifies email service by publishing a message on message broker and doesn't wait for it to finish and continue doing its own processing if any.

Decoupling

This way of communication decouples the Producer from Consumer i.e. they have no knowledge of each other and communicate through a message broker. Components in such an architecture are oblivious to each other and are easier to maintain, extend and debug.

Message broker multiple consumers

We just added two more components which are interested in listening order placed message. SMS service now can send a message when an order is placed and same way Inventory service can start packaging and deliver the goods. We can see how easy it is to extend the system to multiple consumers listening to the same kind of messages without disturbing the producer since Message broker is the only part which consumers are concerned about. These decoupled components can evolve differently with different languages, different frameworks or by different teams.

Reliability

Both Producer(s) and Consumer(s) need not interact with message breaker simultaneously. Producer and Consumer are totally isolated and need not follow each other states. Producer can just put the message on the queue and then continue processing. Consumer which is working independently will pick up the message from the queue when it is able to process them. If Consumer isn't ready or not available to process yet, those messages will be stored and delivered when Consumer is healthy and available to process the messages.

Scenarios, where you can't afford to miss these messages, can be a strong use case for Message queues. Ex: GDPR compliance. Let's say in a microservices architecture, Our application has the user data saved across multiple DBs. When a user from the provided user interface triggers a Delete my data action, that action has to be propagated to multiple services to delete data from multiple DBs. What if one of the services is not healthy and not able to receive any HTTP action, It'll not be triggered and data remain undeleted which makes the whole system GDPR uncompliant. 😵

Reliable delivery of messages would be beneficial in this scenario. On the initial trigger, a delete all data can be published to all the queues. At the appropriate time, when consumers are healthy and ready to process messages, they will be delivered to and processed by consumers.

Conclusion

We saw how async processing helps in building interactive systems by reducing the waiting time for end-users. Along with that, we also saw how message queues bring in decoupling resulting in a well structured, extendable, maintainable and easy to debug systems.
Last but not least, Reliability ensures No message left behind. :)

Top comments (0)