Microservices Communication: Synchronous vs Asynchronous
Microservices communicate through various patterns. After building production microservices, here’s when to use synchronous vs asynchronous communication.
Communication Patterns
Synchronous Communication
Characteristics:
- Request-response pattern
- Blocking calls
- Immediate response
- Tight coupling
Use cases:
- User-facing requests
- Real-time operations
- Simple queries
Asynchronous Communication
Characteristics:
- Event-driven
- Non-blocking
- Eventual consistency
- Loose coupling
Use cases:
- Background processing
- Event notifications
- Long-running tasks
Synchronous Patterns
REST API
// Client
const response = await fetch('http://user-service/api/users/123');
const user = await response.json();
// Server
app.get('/api/users/:id', async (req, res) => {
const user = await userService.getUser(req.params.id);
res.json(user);
});
Pros:
- Simple to implement
- Standard HTTP
- Easy to debug
- Good tooling
Cons:
- Tight coupling
- Cascading failures
- No guaranteed delivery
gRPC
// Client
const user = await userClient.getUser({ userId: '123' });
// Server
async getUser(call, callback) {
const user = await userService.getUser(call.request.userId);
callback(null, user);
}
Pros:
- High performance
- Type safety
- Streaming support
- Efficient serialization
Cons:
- More complex
- Less tooling
- HTTP/2 required
Asynchronous Patterns
Message Queues
// Producer
await sqs.sendMessage({
QueueUrl: queueUrl,
MessageBody: JSON.stringify({
event: 'user.created',
userId: '123',
data: userData
})
}).promise();
// Consumer
const messages = await sqs.receiveMessage({
QueueUrl: queueUrl
}).promise();
for (const message of messages.Messages) {
const event = JSON.parse(message.Body);
await handleEvent(event);
await sqs.deleteMessage({
QueueUrl: queueUrl,
ReceiptHandle: message.ReceiptHandle
}).promise();
}
Pros:
- Decoupled services
- Guaranteed delivery
- Scalable
- Resilient
Cons:
- Eventual consistency
- More complex
- Debugging harder
Event-Driven
// Publisher
await eventBus.publish({
type: 'OrderCreated',
orderId: '123',
userId: '456',
total: 99.99
});
// Subscriber
eventBus.subscribe('OrderCreated', async (event) => {
await inventoryService.reserveItems(event.orderId);
await emailService.sendConfirmation(event.userId);
await analyticsService.trackOrder(event.orderId);
});
Pros:
- Loose coupling
- Scalable
- Flexible
- Eventual consistency
Cons:
- Complex debugging
- Event ordering
- Duplicate events
Hybrid Approach
Request-Response + Events
// Synchronous for user request
app.post('/api/orders', async (req, res) => {
// Create order synchronously
const order = await orderService.createOrder(req.body);
// Publish event asynchronously
await eventBus.publish({
type: 'OrderCreated',
orderId: order.id
});
res.json(order);
});
// Asynchronous for background processing
eventBus.subscribe('OrderCreated', async (event) => {
await inventoryService.reserveItems(event.orderId);
await emailService.sendConfirmation(event.userId);
});
When to Use Each
Use Synchronous When:
- User-facing requests - Need immediate response
- Simple queries - Direct data access
- Real-time operations - Immediate feedback
- Strong consistency - ACID transactions
Use Asynchronous When:
- Background processing - Can be delayed
- Event notifications - Fire and forget
- Long-running tasks - Don’t block
- Eventual consistency - Acceptable
Best Practices
- Use REST for APIs - Standard and simple
- Use gRPC for internal - High performance
- Use queues for async - Reliable processing
- Use events for decoupling - Loose coupling
- Handle failures - Retries and DLQs
- Monitor communication - Track latency/errors
- Document contracts - API specifications
- Version APIs - Backward compatibility
Conclusion
Choose communication pattern based on:
- Latency requirements - Sync for immediate
- Consistency needs - Sync for strong consistency
- Coupling tolerance - Async for loose coupling
- Failure handling - Async for resilience
Use synchronous for user-facing, asynchronous for background. The patterns shown here handle production microservices.
Microservices communication patterns from August 2020, covering synchronous and asynchronous approaches.