Avoiding Synchronous Communication in Microservices: A Better Way

harish bhattbhatt
5 min readMay 5, 2024

Our familiarity with monolithic architectures, where communication happens within the same process, might lead us to favor synchronous communication in microservices. However, this approach introduces significant drawbacks in a distributed environment.

Let’s understand issues with synchronous communication with “meeting” example

Meetings — Synchronous communication

Meetings are prime examples of synchronous communication — everyone must be present at the same time and place. Often, meetings can start late (Increased latency) as attendees trickle in, or they may not join at all, leading to postponed or canceled sessions(Failures and downtime). Such delays can reduce productivity and overwhelm employees, especially when multiple meetings (scalability issue) are scheduled in a single day.

This scenario mirrors synchronous communication in distributed systems, where processes must wait for responses before they can proceed, often leading to bottlenecks and decreased efficiency.

Conversely, asynchronous communication methods, such as emails, instant messaging, or shared documents for review, can enhance productivity. These tools allow information sharing and collaboration without the need for simultaneous presence, mirroring asynchronous processes in software systems that operate independently and handle tasks as resources allow, without waiting for immediate responses.

Incorporating more asynchronous communication can significantly increase efficiency, just as reducing the number of synchronous meetings can lead to more productive and less overwhelmed teams in an office environment.

The Pitfalls of Synchronous Communication:

Synchronous communication, where one service waits for a response from another before proceeding, undermines the core benefits of microservices architecture:

Here are some of the key drawbacks:

  • Blocking Nature: Synchronous calls are blocking, meaning the calling service must wait for the response before proceeding. This can lead to increased latency and poor user experience, especially if the chain of communication is long or if any service in the chain is slow or unresponsive.
  • Resource Intensive: Services must be provisioned to handle peak loads because synchronous communication requires real-time processing. This can lead to inefficiencies and increased costs as resources must be allocated even during non-peak times to handle potential load spikes.
  • Cascading Failures: The interdependencies created by synchronous calls can lead to cascading failures. If one service fails, it can cause a chain reaction that impacts other services, potentially leading to system-wide outages.
  • Strong Coupling: Synchronous communication often results in tightly coupled system components. Changes in one service, such as API updates or downtime, can directly affect all other services that depend on it, complicating updates and maintenance.
  • Complex Error Handling: Handling errors in synchronous communication is more complex because each service in the call chain must be able to respond appropriately to failures in other services. This often requires implementing sophisticated error handling and retry mechanisms to maintain system stability.
  • Scalability Challenges: Scaling a system based on synchronous communication can be challenging, as it requires scaling all dependent services simultaneously. This is less flexible compared to asynchronous systems, where services can scale independently based on demand.

Any time you have a number of synchronous calls between services you will encounter the multiplicative effect of downtime. Simply, this is when the downtime of your system becomes the product of the downtimes of the individual components. You face a choice, making your calls asynchronous or managing the downtime.

Alternatives to Synchronous Communication:

Here are several strategies to decouple microservices and achieve asynchronous communication:

  1. Reassess Microservice Boundaries
  • Consider monolithic architecture if functionality is not very complex or you can manage modular architecture (and in many cases, this is the right approach)
  • Consider merging tightly coupled services or refining service boundaries to reduce inter-service communication.

2. Event-Driven Architecture:

  • Services publish events (e.g., order placed) that are subscribed to by other services (e.g., payment processing).
  • This decoupling allows services to operate independently without waiting for responses.

2. Shared Data:

  • If services frequently need to access shared data, using a common database might be preferable.
  • This approach, however, trades off some degree of decoupling for the convenience of shared access, potentially reintroducing some forms of coupling.

3. Data Copies:

  • Maintain a copy of necessary data within the service or utilize read-only replicas.
  • Data replication, change feeds, and events can be employed to keep copies synchronized.

4. Distributed Cache:

  • Cache frequently accessed data to minimize communication between services.
  • Utilize in-process caching with Time-To-Live (TTL) and leverage distributed caches for broader access.

Trade-offs and Considerations:

Each approach has its trade-offs:

  • Eventual Consistency: Asynchronous communication leads to eventual consistency rather than immediate consistency. This means there can be delays before data is updated across all parts of the system, which might not be suitable for situations where up-to-date information is critical.
  • Complexity in Error Handling and Monitoring: Tracing and monitoring asynchronous systems can be more complex because the decoupled nature of services means that messages might not be processed immediately or in the order they were sent. This can make it difficult to track the flow of data and diagnose issues.

Real-World Examples of Asynchronous Communication:

To further illustrate the power of asynchronous communication, consider these compelling examples:

Order Processing: Imagine an e-commerce application where a user adds products to a cart, confirms the order, provides shipping details, and proceeds to payment. Each step can be implemented asynchronously:

  • Adding a product to the cart triggers an “Item Added” event, processed by the cart service independently.
  • Order confirmation publishes an “Order Placed” event, allowing the payment service to initiate the transaction asynchronously.
  • Shipping details are sent as a separate “Shipping Information” event, handled by the fulfillment service independently.

IoT Device Onboarding: In the realm of the Internet of Things (IoT), the onboarding of devices is another area where asynchronous communication shines. The sequence involving device registration, location assignment, sensor data updates, and eventual network connection can be handled through a series of events that are processed as they occur, without waiting for previous steps to complete.

Additionally, situations where one service requires data from another — such as a user profile service needing user data or an order service requiring customer information for validation or enrichment — demonstrate the utility of keeping decentralized copies of data. This design ensures data is eventually consistent, supporting the system’s resilience and scalability.

Choosing the Right Approach:

The optimal approach depends on your specific requirements:

  • Re-evaluate Microservice Boundaries: Consider merging chatty services or opting for a monolith if synchronous communication is unavoidable.
  • Event-Driven Architecture: Embrace asynchronous communication whenever possible for loose coupling and scalability.
  • Shared Data and Data Copies: Utilize these strategies judiciously when frequent data access necessitates them.

Remember:

  • Synchronous communication should generally be avoided in favor of asynchronous patterns to reap the full benefits of microservices architecture.
  • Carefully evaluate trade-offs and choose the approach that best aligns with your specific needs.
  • In scenarios where synchronous communication is unavoidable, prioritize in-process caching and leverage distributed cache mechanisms to minimize reliance on external services.

By adopting these strategies, organizations can build more resilient and scalable microservices architectures, reducing the fragility associated with synchronous communication.

References

https://medium.com/@systemdesignbychk/system-design-a-comprehensive-guide-on-synchronous-asynchronous-microservice-communication-8bda324943b8#:~:text=Synchronous%20Microservice%20Communication,to%20traditional%20client%2Dserver%20interactions.

https://www.youtube.com/watch?v=ewUw0sUxHI4

--

--