Microservices Communication Patterns
Design inter-service communication. Covers synchronous vs asynchronous, API gateways, service discovery, message queues, event buses, and handling distributed failures.
How microservices talk to each other determines whether your architecture is resilient or fragile. Synchronous HTTP calls create tight coupling — if one service is slow, everything is slow. Asynchronous messaging decouples services but adds complexity around ordering, idempotency, and eventual consistency. The right choice depends on the use case.
Sync vs Async
| Factor | Synchronous (HTTP/gRPC) | Asynchronous (Messages/Events) |
|---|---|---|
| Coupling | Tight — caller waits for response | Loose — fire and forget |
| Latency | Adds up serially (A → B → C → D) | Parallel processing |
| Failure handling | Cascading failures | Isolated failures |
| Data consistency | Immediate (request-response) | Eventual consistency |
| Complexity | Lower | Higher (queues, ordering, idempotency) |
| Best for | Queries, reads, simple CRUD | Events, commands, long operations |
Communication Decision Tree
Does the caller need an immediate response?
├── Yes → Is latency critical (< 50ms)?
│ ├── Yes → gRPC (binary, fast)
│ └── No → REST/HTTP (simpler)
└── No → Is ordering important?
├── Yes → Message Queue (Kafka, SQS)
│ with partition key for ordering
└── No → Event Bus (SNS, EventBridge)
for fan-out to multiple consumers
API Gateway
┌───────────────┐
Client ────────────▶ │ API Gateway │
│ │
│ • Auth │
│ • Rate limit │
│ • Routing │
│ • Aggregation │
└───┬───┬───┬───┘
│ │ │
┌──────┘ │ └──────┐
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│User │ │Order │ │Pay │
│Svc │ │Svc │ │Svc │
└──────┘ └──────┘ └──────┘
Message Queue Patterns
Point-to-Point (Command)
Producer ──── message ────▶ [Queue] ────▶ Consumer
(one consumer
processes each
message)
Pub/Sub (Event)
Producer ──── event ────▶ [Topic] ──┬──▶ Consumer A
├──▶ Consumer B
└──▶ Consumer C
(all consumers
receive every event)
Idempotent Consumer
async def handle_order_created(event):
"""Process order event idempotently."""
event_id = event["event_id"]
# Check if already processed
if await db.exists("processed_events", event_id):
logger.info(f"Event {event_id} already processed, skipping")
return
# Process the event
await create_invoice(event["order_id"])
# Mark as processed (idempotency key)
await db.insert("processed_events", {
"event_id": event_id,
"processed_at": datetime.utcnow()
})
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Synchronous chain (A→B→C→D) | Latency = sum of all services, any failure breaks chain | Async where possible, parallelize sync calls |
| Distributed monolith | Microservices that must deploy together | Decouple with events, contract testing |
| No idempotency | Retry causes duplicate processing | Idempotency keys on all consumers |
| Direct service-to-service HTTP | Tight coupling, service discovery complexity | API gateway or service mesh |
| No dead letter queue | Failed messages lost forever | DLQ + monitoring + alerting |
Checklist
- Communication pattern chosen per interaction (sync vs async)
- API gateway for external traffic (auth, rate limiting, routing)
- Service discovery: DNS-based or service mesh
- Async messaging for events and commands (Kafka, SQS, RabbitMQ)
- Idempotent consumers on all message handlers
- Dead letter queue configured with monitoring
- Circuit breakers on all synchronous calls
- Timeout and retry policies per service call
- Contract tests between services
:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For microservices consulting, visit garnetgrid.com. :::