The hardest part of microservices is not building them — it is connecting them. Every inter-service call is a network call, and network calls fail, are slow, and introduce coupling. The communication pattern you choose determines your system’s reliability, latency, and how painful it is to debug when things go wrong.
Most teams default to synchronous REST calls because that is what they know. This works for simple request-response flows but creates fragile dependency chains when service A calls service B which calls service C, and any failure cascades back.
Communication Patterns Overview
| Pattern | When to Use | Tradeoff |
|---|
| Synchronous (REST/gRPC) | Need immediate response | Simple, but creates temporal coupling |
| Asynchronous (message queue) | Can tolerate delay, need reliability | Decoupled, but harder to debug |
| Event-driven (pub/sub) | Multiple consumers, loose coupling | Very decoupled, but eventual consistency |
| Request-reply (async) | Need response but want decoupling | Best of both, but more complex |
Synchronous: REST and gRPC
Synchronous call chain:
Client → API Gateway → Order Service → Payment Service → Bank API
│ │ │
▼ ▼ ▼
Waiting... Waiting... Processing...
│ │ │
▼ ▼ ▼
Response ←───────── Response ←──── Response
Problem: total latency = sum of all service latencies
Problem: if any service is down, the entire request fails
Problem: Bank API takes 3 seconds → entire checkout takes 3+ seconds
When to Use Synchronous
| Use When | Avoid When |
|---|
| User is waiting for a response | Background processing is acceptable |
| Operation must succeed or fail atomically | Multiple independent side effects |
| Simple request-response, few dependencies | Deep call chains (> 2 hops) |
| Read operations (queries, lookups) | Write operations with side effects |
gRPC vs REST
| Feature | REST (HTTP/JSON) | gRPC (HTTP/2 + Protobuf) |
|---|
| Format | JSON (human-readable) | Protobuf (binary, compact) |
| Performance | Good | ~10x faster serialization |
| Streaming | Limited (SSE, WebSockets) | Native bidirectional streaming |
| Schema | Optional (OpenAPI) | Required (proto files) |
| Browser support | Native | Requires grpc-web proxy |
| Best for | Public APIs, simple CRUD | Internal services, high throughput |
Asynchronous: Message Queues
Asynchronous with message queue:
Client → API Gateway → Order Service → [Message Queue] → Payment Service
│ │
▼ ▼
Returns 202 Processes asynchronously
"Order received" Publishes result to queue
│
▼
Order Service picks up result
Updates order status
Queue vs Topic
| Concept | Queue | Topic (Pub/Sub) |
|---|
| Consumers | One (competing consumers) | Many (all subscribers get copy) |
| Pattern | Work distribution | Event notification |
| Delivery | Exactly one consumer processes | All subscribers receive |
| Example | Task queue (send email, process image) | Event bus (order placed, user signed up) |
Message Queue Implementation
# Producer: Order Service publishes order events
import json
import pika
def publish_order_event(order):
connection = pika.BlockingConnection(pika.ConnectionParameters('rabbitmq'))
channel = connection.channel()
channel.queue_declare(queue='order_events', durable=True)
message = {
"event": "order.placed",
"order_id": order.id,
"customer_id": order.customer_id,
"total": str(order.total),
"timestamp": datetime.utcnow().isoformat()
}
channel.basic_publish(
exchange='',
routing_key='order_events',
body=json.dumps(message),
properties=pika.BasicProperties(
delivery_mode=2, # Persistent message
content_type='application/json'
)
)
Event-Driven Architecture
Event-driven: services react to events, not direct calls
Order Service ──publishes──→ "OrderPlaced" event ──→ Event Bus
│
┌──────────────────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Payment Service Inventory Service Email Service
"Charge customer" "Reserve stock" "Send confirmation"
Each service:
- Decides independently what to do with the event
- Can fail without affecting other services
- Can be added/removed without changing the publisher
Event Design
{
"event_id": "evt_abc123",
"event_type": "order.placed",
"event_version": "1.0",
"timestamp": "2024-03-15T14:23:45.123Z",
"source": "order-service",
"correlation_id": "trace-xyz789",
"data": {
"order_id": "ord_456",
"customer_id": "cust_789",
"items": [
{"product_id": "prod_001", "quantity": 2, "price": "49.99"}
],
"total": "99.98",
"currency": "USD"
}
}
The Saga Pattern: Distributed Transactions
When a business process spans multiple services, you cannot use a database transaction. The saga pattern coordinates a sequence of local transactions with compensating actions.
Choreography Saga (event-driven):
Order Service Payment Service Inventory Service
│ │ │
OrderPlaced ──────→ Charge card Reserve items
│ │ │
│ PaymentSucceeded ──→ │
│ │ InventoryReserved
│ │ │
▼ ▼ ▼
Order confirmed Payment recorded Stock updated
If Payment FAILS:
OrderPlaced ──────→ Charge card (FAILS)
│
PaymentFailed ──────→ Cancel order
Release inventory
| Saga Type | Coordination | Best For |
|---|
| Choreography | Events, no central coordinator | Simple flows (2-3 services) |
| Orchestration | Central saga coordinator | Complex flows (4+ services) |
Decision Framework
Do I need an immediate response?
├── Yes: Use synchronous (REST/gRPC)
│ ├── Is it high-throughput internal traffic? → gRPC
│ └── Is it a public API or simple CRUD? → REST
│
└── No: Use asynchronous
├── Does one specific service need to process this? → Message Queue
├── Do multiple services need to react? → Event Bus (pub/sub)
└── Is it a multi-step business process? → Saga pattern
Implementation Checklist