Hello! Welcome back to our course on distributed systems architecture.
Introduction
In our last two lessons, you gained practical, hands-on experience with the two fundamental communication patterns in distributed systems. First, you implemented a synchronous request-response pattern for a trading scenario, where the TradingService had to block and wait for a price from the MarketDataService. Then, you refactored this into an asynchronous message-passing pattern using RabbitMQ, where the services were decoupled, and communication happened via a message queue.
Having built both, you've likely developed an intuition for their differences. Today, our goal is to formalize that intuition. We will systematically compare the trade-offs between these two patterns, giving you a robust framework for making architectural decisions.
Learning Outcome:
Compare the trade-offs between synchronous and asynchronous communication patterns in terms of latency, throughput, coupling, and fault tolerance.
We will analyze each of these four dimensions, connecting them back to the trading scenario you implemented and drawing on your experience with complex financial systems. Understanding these trade-offs is not just an academic exercise; it is the very essence of architectural design.
1. Synchronous vs. Asynchronous: A Quick Refresher
Let's begin by solidifying the definitions of our two communication styles.
- Synchronous (or Blocking) Communication: The client sends a request and then waits—or blocks—until it receives a response from the server. The client's execution is paused during this time. The request-response pattern you implemented using HTTP is a classic example.
- Asynchronous (or Non-Blocking) Communication: The client sends a message and immediately continues with its own processing, without waiting for a reply. The reply, if any, is handled later, perhaps through a callback or by polling for a result. The message queue pattern you built with RabbitMQ is a prime example.
To visualize this distinction, let's watch a short video that introduces these concepts clearly.
What are the types of communication for microservices? (Intro to Microservices - Part 2) sudoCODE
The video 'What are the types of communication for microservices?' from the sudoCODE channel provides a great high-level overview of these two communication styles.
Please watch from the beginning until 01:25. This section defines synchronous blocking and asynchronous non-blocking communication, setting the stage for our comparison.
Now that we have the core definitions refreshed, let's dive into the trade-offs.
2. The Four Dimensions of Comparison
The choice between synchronous and asynchronous communication is a multi-faceted decision. We will analyze it across four critical dimensions: coupling, latency, fault tolerance, and throughput.
The article "Designing Distributed Systems: Sagas and Trade-Offs" provides excellent summary tables for these trade-offs. We will refer to its analysis as we go.
Designing Distributed Systems: Sagas and Trade-Offs
This article by Rand Azraik clearly lays out the pros and cons of each communication style. It will serve as a great reference for our discussion.
For now, just skim the two tables under the 'Trade-offs' subheadings for both 'Synchronous Communication' and 'Asynchronous Communication'. We will be discussing these points in detail.
A. Coupling
Coupling refers to the degree of interdependence between two services. In distributed systems, we are primarily concerned with temporal coupling.
-
Synchronous: This pattern creates tight temporal coupling. For the interaction to succeed, both the client and the server must be running and available at the exact same time. In your first implementation, if the
MarketDataServicewas down or slow, theTradingServicewas directly impacted—it was stuck waiting. This creates a fragile system where one service's failure can easily propagate. -
Asynchronous: This pattern enables loose temporal coupling. The producer and consumer do not need to be available simultaneously. The message broker acts as an intermediary, holding messages in a queue until the consumer is ready. In your second implementation, the
MarketDataServicecould publish price updates even if theTradingServicewas offline for a restart. When theTradingServicecame back online, it could begin processing the queued messages. This decoupling is a major driver of resilience.
The video you watched earlier has a good segment on how event-based systems achieve this decoupling.
What are the types of communication for microservices? (Intro to Microservices - Part 2) sudoCODE
Let's revisit the sudoCODE video to focus on its explanation of decoupling.
Please watch the section from 05:56 to 07:30. It explains how in an event-based asynchronous model, the producer (Order Service) doesn't need to know anything about the consumers, leading to a 'hugely decoupled' system.
B. Latency
Latency is the time it takes to get a response. The perception of latency, however, depends on what kind of response the user needs.
-
Synchronous: This pattern provides low latency for a definitive outcome. When the call returns, the client knows immediately whether the operation succeeded or failed. For a user executing an FX trade, this is critical. They need to know now if their trade was filled at the requested price. The trade-off is that the overall time-to-response is the sum of the network and processing times of all services in the call chain.
-
Asynchronous: This pattern provides low latency for acknowledgement, but high latency for the final outcome. The client sends a message and quickly gets a response like "Your request has been accepted and will be processed." The actual work happens later. This introduces eventual consistency, where the system state will become consistent over time. This is perfect for actions like sending a trade confirmation email—the user doesn't need to wait for the email to be sent before they can continue using the application.
C. Fault Tolerance (Resilience)
Fault tolerance is the system's ability to continue operating correctly in the event of failures (e.g., a service crashing or a network outage).
-
Synchronous: This pattern has poor fault tolerance. As mentioned in the
sudoCODEvideo, synchronous calls can create long chains. A failure in any single service in that chain will typically cause the entire workflow to fail. This is known as a cascading failure. The caller must implement complex retry and timeout logic to handle this, as you did in the first lesson. -
Asynchronous: This pattern has high fault tolerance. The message broker acts as a shock absorber. If a consumer service fails, the producer can often continue to operate normally, and messages simply accumulate in the queue. Once the consumer recovers, it can resume processing from where it left off. This containment of failure is a key reason for adopting asynchronous patterns.
D. Throughput & Scalability
Throughput is the number of operations a system can handle per unit of time. Scalability is the ability to increase throughput by adding more resources.
-
Synchronous: Throughput is limited by the slowest service in the call chain. The client's resources (e.g., network connections, threads) are occupied while waiting, which can become a bottleneck under high load. Scaling can be difficult because you might need to scale the entire chain of services together.
-
Asynchronous: This pattern enables high throughput and independent scalability. The producer's job is just to publish a message, which is a very fast operation. It can accept new requests without waiting for previous ones to be fully processed. On the other side, you can increase processing capacity by simply adding more consumer instances to read from the same queue. This is known as the competing consumers pattern, which we will implement in the next module.
3. A Framework for Choosing
So, which pattern should you use? The answer is almost always "it depends on the context." Most complex systems, like the financial platforms you've worked on, use a hybrid approach. The key is to choose the right pattern for the right job.
The following article provides a clear, actionable guide for making this decision.
Synchronous vs asynchronous communications
The article 'Synchronous vs asynchronous communications' from TechTarget offers pragmatic advice on this topic. It reinforces the trade-offs and discusses how these patterns coexist in modern architectures.
Please read the section 'Comparing synchronous vs asynchronous communication'. It does an excellent job of framing the choice as a trade-off between architectural simplicity and resilience/scalability and notes that the two patterns are complementary.
To summarize and build on this, here is a simple decision framework:
Use synchronous communication when:
- The client needs an immediate and definitive response to continue its flow (e.g., user authentication, validating a credit card).
- The operation is naturally atomic and simple, involving few services.
- The workflow is read-heavy, and the client is fetching data required for immediate display.
Use asynchronous communication when:
- Resilience and availability are more critical than immediate feedback.
- The task can be processed in the background (e.g., sending notifications, generating reports, data replication).
- You need to absorb spikes in load and ensure high throughput.
- You want to decouple services to allow for independent development, deployment, and scaling.
Your Turn: Apply the Framework
Let's apply this framework to a scenario from your background. Consider the full lifecycle of a foreign exchange trade at a platform like Revolut:
- A retail customer requests a quote for EUR/USD and executes a trade on their mobile app.
- The internal treasury system aggregates this trade with others and executes a larger block trade with an external liquidity provider to manage the firm's exposure.
- A confirmation is sent to the customer.
- The trade is reported to a regulatory body at the end of the day.
Think about the communication patterns for each step. Which parts of this workflow would you design to be synchronous, and which would be asynchronous? Why? Justify your choices based on the trade-offs of latency, coupling, fault tolerance, and throughput.
Take a few minutes to think it through before reading my analysis below.
Click here to see my analysis
-
Step 1 (Customer Trade Execution): This should be synchronous. The user needs immediate, blocking confirmation of the rate and that their trade was successfully executed. The perceived latency must be low, and the outcome must be definitive. The tight coupling to the pricing and execution engine is acceptable here because the user's workflow depends on it.
-
Step 2 (Internal Treasury Hedging): This is a prime candidate for asynchronous communication. The customer's trade can publish an
FXTradeExecutedevent. The treasury system can consume these events, aggregate them, and execute hedges when appropriate. This decouples the customer-facing system from the internal risk management system, improving resilience and scalability. The customer doesn't need to wait for the firm's internal hedge to be completed. -
Step 3 (Customer Confirmation): This should be asynchronous. The same
FXTradeExecutedevent (or a subsequentTradeSettledevent) can be consumed by a notification service that sends an email or push notification. There is no reason to make the user wait for the email to be sent. -
Step 4 (Regulatory Reporting): This is definitively asynchronous. This is a background, batch-oriented process. Events from all settled trades can be written to a durable message log or queue, and a separate reporting service can consume them at its own pace to generate the required reports.
Conclusion
You have now moved from implementing communication patterns to analyzing them like an architect. This ability to reason about trade-offs is fundamental to designing robust and scalable distributed systems.
Key Takeaways:
- Synchronous communication offers simplicity and immediate, definitive feedback. Its price is tight coupling, which leads to lower fault tolerance and potential performance bottlenecks.
- Asynchronous communication offers resilience, high throughput, and scalability through loose coupling. Its price is increased complexity and eventual consistency.
- The choice is not about which is "better" but which is fitter for the specific interaction's requirements.
- Most real-world distributed systems are hybrids, using synchronous patterns for user-facing, immediate-response interactions and asynchronous patterns for background processing, decoupling, and resilience.
Preview of the Next Lesson:
In our asynchronous trading example, what would happen if the TradingService crashed right after executing a trade but before it could mark the message as processed? The message broker might redeliver the message, causing a duplicate trade. In a financial system, this is unacceptable.
In our next lesson, we will tackle this problem by exploring the concept of idempotency. You will learn how to design consumers that can safely process the same message multiple times, a critical technique for building reliable asynchronous systems.
Can't find a good explanation? Sign up and we'll make it for you
Sign up