The world is asynchronous. Many workflows and business processes you encounter out in the world are long-running and driven by asynchronous systems. Yet as developers, we’re still often writing procedural and synchronous code to model these business processes. I will give one of my favorite examples of going out to eat at a restaurant to illustrate this and how this can be applied to software.
Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.
Asynchronous Chicken Wings?
One of my favorite ways to explain asynchronous workflows is to use a restaurant as an example. There are asynchronous workflows everywhere when you eat at a restaurant.
However, many developers can get caught in the trap of only working with synchronous blocking RPC calls between services or boundaries when building systems.
To illustrate, let’s use the restaurant example. We can think of two different service boundaries. The waiter/waitress is one; the kitchen is another. If you were making blocking RPC calls between them, that would mean that when the customer places their food order with the waiter/waitress, they would immediately go to the kitchen and tell the cooking staff. The waiter/waitress would stand there waiting for the food to be finished so they could bring it to the customers’ table. The customer is doing nothing else this entire time; they are simply waiting.
That’s not how a restaurant works. Interactions between the customer, waiter/waitress, and the kitchen aren’t blocking.
If you’re developing a microservices or service-oriented system and making service-to-service calls with HTTP or gRPC then you’re making blocking calls.
We know those interactions at a restaurant aren’t blocking. They are asynchronous. When a waiter places an order in the kitchen, they don’t stand there waiting for the food. So why would we write systems that communicate using blocking calls between them?
To illustrate how the asynchronous workflow really works, first a waiter/waitress goes to a customers table and gets their order.
They may right afterward go to another customer’s table and collect their order as well.
Now that they have two orders from different customers, they proceed to place the order with the kitchen.
At this time, our customers are free to do whatever they want. The waiter/waitress might be doing some other work unrelated to attending tables. The kitchen, at this point, is working on the two orders that were placed. Once they are finished, the waiter/waitress then gets the food from the kitchen.
They would then immediately go and bring the food to the customers’ table.
The same process would occur for the other customer order. Once it’s ready, the waiter/waitress would get the food from the kitchen and bring it to the customers’ table.
Nothing is blocking. The waiter/waitress is free to do other tasks while food is being cooked.
You can build asynchronous workflows using messaging. Sending commands and publishing events to a message broker. This allows us to have our interactions asynchronous and in isolation. We aren’t temporally coupled between services. Each service can work independently without requiring the other.
When the client tells the wait staff (waiter/waitress) their order, they persist that into their database.
They then could create a command for the kitchen boundary to prepare the food.
Once the message is sent to the broker, the client and wait staff interaction is complete. At this point, the wait staff isn’t concerned about the kitchen; it knows it will consume the message which tells it to prepare the food. If the kitchen is flooded with messages, preparing the food might take longer than expected. But because this is asynchronous, it doesn’t mean it will fail, just that it will take longer.
The kitchen will then consume the message and prepare the food.
Once the food is ready, the kitchen can publish an event back to the broker or possibly provide a reply to the wait staff to notify them that the food is ready.
All these interactions are asynchronous and non-blocking.
Long-running business processes and workflows are everywhere. Just look to the real world sometimes for examples of how they work. You can often use them to illustrate how a message or event-driven system would work. Being loosely coupled between services can make your system much more resilient to failures and influx of load.
Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out the YouTube Membership or Patreon for more info.
You also might like
- Workflow Orchestration for Resilient Systems
- Asynchronous Request-Response Pattern for Non-Blocking Workflows