In a real-world system, different components need to communicate with each other.
For example, an Order Service may need to communicate with a Payment Service, or an ASP.NET Core API may need to communicate with SQL Server.
There are two common communication patterns:
- Synchronous Communication
- Asynchronous Communication
What Is Synchronous Communication?
In synchronous communication, the caller sends a request and waits for the other component to respond.
A simple example is an HTTP API call.
sequenceDiagram
User->>OrderAPI: Create order
OrderAPI->>PaymentAPI: Process payment
PaymentAPI-->>OrderAPI: Payment result
OrderAPI-->>User: Order result
The Order API waits for the Payment API to respond before continuing.
Real-World Example
Imagine a user is placing an order.
The application needs to validate payment before confirming the order.
The flow could be:
flowchart LR
User --> OrderAPI
OrderAPI --> PaymentAPI
PaymentAPI --> Bank
Bank --> PaymentAPI
PaymentAPI --> OrderAPI
OrderAPI --> User
The user receives the final response after the payment operation returns.
ASP.NET Core Example
Suppose our Order API calls a Payment API.
[HttpPost]
public async Task CreateOrder(OrderRequest request)
{
var paymentResponse = await _httpClient.PostAsJsonAsync(
"https://payment-service/api/payments",
request);
if (!paymentResponse.IsSuccessStatusCode)
{
return BadRequest("Payment failed");
}
var order = new Order
{
ProductId = request.ProductId,
Amount = request.Amount,
Status = "Confirmed"
};
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return Ok(order);
}
The important point is that the Order API waits for the payment response.
Advantages of Synchronous Communication
- Simple request and response model.
- Easy to understand.
- The caller immediately knows the result.
- Useful when the result is required before continuing.
- HTTP APIs are straightforward to implement and test.
Problems With Synchronous Communication
The biggest problem is dependency.
If Payment API becomes slow, Order API can also become slow.
flowchart LR
User --> OrderAPI
OrderAPI --> PaymentAPI
PaymentAPI --> SlowService
Now the request may wait for the entire chain to complete.
If the Payment API takes 5 seconds, the user may also wait several seconds.
What Happens When the Service Is Down?
Suppose OrderAPI calls PaymentAPI but PaymentAPI is unavailable.
sequenceDiagram
User->>OrderAPI: Create order
OrderAPI->>PaymentAPI: Process payment
PaymentAPI-->>OrderAPI: No response
OrderAPI-->>User: Request failed
The Order API must handle the failure using appropriate timeouts, retries or other recovery mechanisms.
What Is Asynchronous Communication?
In asynchronous communication, the sender does not need to wait for the receiving component to finish processing the operation.
A message can be placed into a queue.
flowchart LR
OrderAPI --> MessageQueue
MessageQueue --> PaymentWorker
The Order API can continue after successfully placing the message into the queue.
Real-World Example
Suppose a user places an order.
After the order is created, we need to:
- Send an email.
- Send an SMS.
- Update analytics.
- Notify the warehouse.
These operations may not need to happen before the user receives the order response.
flowchart LR
User --> OrderAPI
OrderAPI --> Database
OrderAPI --> Queue
Queue --> EmailService
Queue --> SMSService
Queue --> Analytics
Queue --> Warehouse
The API can create the order and publish messages.
Other services process those messages independently.
ASP.NET Core Example With a Queue
Imagine that after creating an order, we publish an event:
var order = new Order
{
ProductId = request.ProductId,
Amount = request.Amount,
Status = "Created"
};
_context.Orders.Add(order);
await _context.SaveChangesAsync();
await messageQueue.PublishAsync(
new OrderCreatedEvent
{
OrderId = order.Id
});
return Ok(order);
A background worker can process the message:
public async Task ProcessOrder(OrderCreatedEvent message)
{
await notificationService
.SendOrderConfirmation(message.OrderId);
}
The API does not need to wait for the email service to finish.
Queue-Based Architecture
flowchart LR
User --> OrderAPI
OrderAPI --> Queue
Queue --> Worker
Worker --> EmailService
The queue acts as a buffer between the producer and consumer.
If the EmailService is temporarily unavailable, messages can remain in the queue and be processed later, depending on the queue technology and configuration.
Azure or AWS Example
In a cloud environment, we can use managed messaging services.
For example, an architecture could use:
- AWS SQS
- Amazon SNS
- Azure Service Bus
- RabbitMQ
- Kafka
The exact technology depends on the communication requirements.
flowchart LR
API --> Queue
Queue --> Consumer1
Queue --> Consumer2
Queue --> Consumer3
Synchronous vs Asynchronous
| Synchronous | Asynchronous |
|---|---|
| Caller waits for response | Caller does not need to wait for processing |
| Usually request and response | Usually message or event based |
| Simple to implement | Requires messaging infrastructure |
| Immediate result | Result may be processed later |
| Strong dependency between caller and receiver | Components can be more loosely coupled |
| Failure can directly affect caller | Messages can often be retried later |
When Should We Use Synchronous Communication?
Synchronous communication is useful when the caller needs the result immediately.
Examples include:
- Login
- Checking account balance
- Getting product details
- Validating a request
- Checking payment status when the result is immediately required
sequenceDiagram
User->>API: Get balance
API->>BankService: Get balance
BankService-->>API: Balance
API-->>User: Balance
When Should We Use Asynchronous Communication?
Asynchronous communication is useful when the operation can happen later.
Examples include:
- Email notifications
- SMS notifications
- Report generation
- Analytics processing
- Background image processing
- Order events
- Long-running jobs
flowchart LR
User --> API
API --> Queue
Queue --> BackgroundWorker
A Common Real-World Example
Consider an online shopping application.
When a customer places an order:
Synchronous:
- Validate the request.
- Check product availability.
- Process payment when immediate confirmation is required.
- Return the order result.
Asynchronous:
- Send email.
- Send SMS.
- Update analytics.
- Generate invoices.
- Notify downstream systems.
flowchart LR
User --> OrderAPI
OrderAPI --> Payment
OrderAPI --> Database
OrderAPI --> Queue
Queue --> Email
Queue --> Analytics
Queue --> Invoice
Important Problem: Duplicate Messages
Asynchronous systems need to handle duplicate messages.
For example, a worker may process a message but fail before confirming completion to the queue.
The queue may deliver the message again.
flowchart TD
Queue --> Worker
Worker --> Process
Process --> Failure
Failure --> Queue
Queue --> Worker
The consumer should therefore be designed so that processing the same message again does not create an unintended result.
This is another situation where idempotency becomes important.
Asynchronous Does Not Always Mean Faster
This is an important point.
Asynchronous communication can reduce the time the user waits for certain operations, but the actual background processing may still take time.
For example:
API response = 100 ms
Email processing = 2 seconds
The user may receive the API response quickly, while the email is processed afterward.
The total business workflow still takes longer, but the user does not need to wait for every step.
Key Takeaways
- Synchronous communication waits for a response.
- Asynchronous communication allows processing to happen independently.
- HTTP APIs are commonly used for synchronous communication.
- Queues and messaging systems are commonly used for asynchronous communication.
- Synchronous communication is useful when an immediate result is required.
- Asynchronous communication is useful for background and long-running work.
- Asynchronous systems require careful handling of retries and duplicate messages.
- Both communication patterns can be used together in the same system.
Conclusion
Synchronous communication is like making a phone call: you ask a question and wait for the answer.
Asynchronous communication is more like sending a message: you send the work and allow another component to process it when it can.
Real-world systems commonly use both.
For operations where the user immediately needs a result, synchronous communication is often appropriate. For notifications, background processing and other work that does not need to block the user request, asynchronous communication can provide better decoupling and responsiveness.
With this article, we have completed the Level 1 — Fundamentals series.