Microservices Communication Patterns: Sync, Async, and Event-Driven
Master the communication patterns between microservices. Synchronous REST/gRPC, asynchronous messaging with RabbitMQ/NATS, event sourcing, saga pattern,...
The Communication Challenge
When you split a monolith into microservices, the biggest challenge is how services communicate. The wrong pattern leads to distributed monoliths, cascading failures, and debugging nightmares.
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="220" rx="12" fill="#1a1a2e"/><rect x="230" y="15" width="140" height="35" rx="8" fill="#6366f1" opacity="0.9"/><text x="300" y="38" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui" font-weight="bold">API Gateway</text><rect x="30" y="80" width="100" height="50" rx="8" fill="#3b82f6" opacity="0.8"/><text x="80" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Auth</text><text x="80" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Service</text><rect x="160" y="80" width="100" height="50" rx="8" fill="#a855f7" opacity="0.8"/><text x="210" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">User</text><text x="210" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Service</text><rect x="290" y="80" width="100" height="50" rx="8" fill="#2dd4bf" opacity="0.8"/><text x="340" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Order</text><text x="340" y="115" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service</text><rect x="420" y="80" width="100" height="50" rx="8" fill="#f59e0b" opacity="0.8"/><text x="470" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Payment</text><text x="470" y="115" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service</text><line x1="265" y1="50" x2="80" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><line x1="285" y1="50" x2="210" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><line x1="315" y1="50" x2="340" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><line x1="335" y1="50" x2="470" y2="78" stroke="#e2e8f0" stroke-width="1" opacity="0.5"/><ellipse cx="80" cy="175" rx="35" ry="12" fill="none" stroke="#3b82f6" stroke-width="1.5"/><line x1="45" y1="175" x2="45" y2="190" stroke="#3b82f6" stroke-width="1.5"/><line x1="115" y1="175" x2="115" y2="190" stroke="#3b82f6" stroke-width="1.5"/><ellipse cx="80" cy="190" rx="35" ry="12" fill="none" stroke="#3b82f6" stroke-width="1.5"/><line x1="80" y1="130" x2="80" y2="163" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><ellipse cx="340" cy="175" rx="35" ry="12" fill="none" stroke="#2dd4bf" stroke-width="1.5"/><line x1="305" y1="175" x2="305" y2="190" stroke="#2dd4bf" stroke-width="1.5"/><line x1="375" y1="175" x2="375" y2="190" stroke="#2dd4bf" stroke-width="1.5"/><ellipse cx="340" cy="190" rx="35" ry="12" fill="none" stroke="#2dd4bf" stroke-width="1.5"/><line x1="340" y1="130" x2="340" y2="163" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><rect x="155" y="160" width="150" height="30" rx="6" fill="#a855f7" opacity="0.3"/><text x="230" y="180" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">Message Bus / Events</text><line x1="210" y1="130" x2="210" y2="158" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><line x1="470" y1="130" x2="470" y2="175" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3,3"/><line x1="305" y1="175" x2="470" y2="175" stroke="#94a3b8" stroke-width="0.5" stroke-dasharray="3,3" opacity="0.3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Microservices architecture: independent services communicate through an API gateway and event bus.</p></div>
Pattern 1: Synchronous Request-Response
The simplest pattern. Service A calls Service B and waits for a response.
Order Service --HTTP/gRPC--> Payment Service
| |
|<-------- Response ---------|
|
|--HTTP/gRPC--> Inventory Service
| |
|<-------- Response ---------|// Order Service calling Payment Service
async function createOrder(orderData: OrderInput): Promise<Order> {
// Call payment service (synchronous)
const paymentResult = await fetch('http://payment-service:3000/api/charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: orderData.total,
currency: 'USD',
customerId: orderData.customerId,
}),
signal: AbortSignal.timeout(5000), // 5 second timeout
});
if (!paymentResult.ok) {
throw new PaymentFailedError('Payment declined');
}
// Call inventory service (synchronous)
const inventoryResult = await fetch('http://inventory-service:3000/api/reserve', {
method: 'POST',
body: JSON.stringify({ items: orderData.items }),
signal: AbortSignal.timeout(5000),
});
if (!inventoryResult.ok) {
// Compensate: refund the payment
await fetch('http://payment-service:3000/api/refund', {
method: 'POST',
body: JSON.stringify({ paymentId: paymentResult.id }),
});
throw new InventoryError('Items not available');
}
return createOrderRecord(orderData, paymentResult, inventoryResult);
}When to use: Simple CRUD operations, queries that need immediate results, low-latency requirements.
Problems: Cascading failures (if Payment Service is down, Order Service fails), temporal coupling (both services must be running), increased latency (sequential calls).
Pattern 2: Asynchronous Messaging
Services communicate via a message broker. The sender does not wait for a response.
Order Service --publish--> [Message Broker] --consume--> Payment Service
--consume--> Inventory Service
--consume--> Notification Service// Order Service publishes event (NATS example)
import { connect, JSONCodec } from 'nats';
const nc = await connect({ servers: 'nats://nats:4222' });
const codec = JSONCodec();
async function createOrder(orderData: OrderInput): Promise<Order> {
// Save order with 'pending' status
const order = await db.orders.create({
...orderData,
status: 'pending',
});
// Publish event (fire and forget)
nc.publish('orders.created', codec.encode({
orderId: order.id,
customerId: order.customerId,
items: order.items,
total: order.total,
timestamp: new Date().toISOString(),
}));
return order; // Return immediately, processing happens async
}// Payment Service subscribes to events
const sub = nc.subscribe('orders.created');
for await (const msg of sub) {
const order = codec.decode(msg.data);
try {
const payment = await processPayment(order);
// Publish payment result
nc.publish('payments.completed', codec.encode({
orderId: order.orderId,
paymentId: payment.id,
status: 'success',
}));
} catch (error) {
nc.publish('payments.failed', codec.encode({
orderId: order.orderId,
reason: error.message,
}));
}
}When to use: When services do not need immediate responses, fire-and-forget notifications, high-throughput processing, decoupled systems.
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 180" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="180" rx="12" fill="#1a1a2e"/><rect x="20" y="20" width="70" height="35" rx="6" fill="#3b82f6" opacity="0.8"/><text x="55" y="42" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Web</text><rect x="20" y="65" width="70" height="35" rx="6" fill="#3b82f6" opacity="0.8"/><text x="55" y="87" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Mobile</text><rect x="20" y="110" width="70" height="35" rx="6" fill="#3b82f6" opacity="0.8"/><text x="55" y="132" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">IoT</text><rect x="150" y="20" width="120" height="130" rx="10" fill="#6366f1" opacity="0.9"/><text x="210" y="50" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui" font-weight="bold">Gateway</text><line x1="165" y1="60" x2="255" y2="60" stroke="#ffffff" stroke-width="0.5" opacity="0.3"/><text x="210" y="80" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Rate Limit</text><text x="210" y="95" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Auth</text><text x="210" y="110" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Load Balance</text><text x="210" y="125" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Transform</text><text x="210" y="140" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">Cache</text><rect x="340" y="15" width="95" height="35" rx="6" fill="#a855f7" opacity="0.8"/><text x="387" y="37" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Service A</text><rect x="340" y="60" width="95" height="35" rx="6" fill="#2dd4bf" opacity="0.8"/><text x="387" y="82" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service B</text><rect x="340" y="105" width="95" height="35" rx="6" fill="#f59e0b" opacity="0.8"/><text x="387" y="127" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Service C</text><rect x="490" y="55" width="80" height="45" rx="6" fill="none" stroke="#e2e8f0" stroke-width="1"/><text x="530" y="82" text-anchor="middle" fill="#e2e8f0" font-size="10" font-family="system-ui">DB / Cache</text><defs><marker id="arrow7" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="92" y1="37" x2="148" y2="55" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="92" y1="82" x2="148" y2="85" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="92" y1="127" x2="148" y2="115" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="272" y1="55" x2="338" y2="32" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="272" y1="85" x2="338" y2="77" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="272" y1="115" x2="338" y2="122" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/><line x1="437" y1="77" x2="488" y2="77" stroke="#e2e8f0" stroke-width="1" marker-end="url(#arrow7)"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">API gateway pattern: a single entry point handles auth, rate limiting, and routing to backend services.</p></div>
Pattern 3: Event-Driven with Event Store
Events are the source of truth. Services react to events and maintain their own state.
[Event Store]
orders.created → Payment Service (charge customer)
payments.completed → Inventory Service (reserve items)
inventory.reserved → Shipping Service (create shipment)
shipment.created → Notification Service (email customer)// Event store with NATS JetStream
import { connect, AckPolicy, DeliverPolicy } from 'nats';
const nc = await connect({ servers: 'nats://nats:4222' });
const js = nc.jetstream();
const jsm = await nc.jetstreamManager();
// Create stream (durable event store)
await jsm.streams.add({
name: 'ORDERS',
subjects: ['orders.>'],
retention: 'limits',
max_age: 30 * 24 * 60 * 60 * 1e9, // 30 days in nanoseconds
storage: 'file',
});
// Publish event
await js.publish('orders.created', codec.encode(orderEvent));
// Consume with durable consumer (survives restarts)
const consumer = await js.consumers.get('ORDERS', 'payment-service');
const messages = await consumer.consume();
for await (const msg of messages) {
const event = codec.decode(msg.data);
await processEvent(event);
msg.ack(); // Acknowledge after processing
}Pattern 4: Saga Pattern
For distributed transactions across services, use the Saga pattern. Each step has a compensating action for rollback.
Create Order → Charge Payment → Reserve Inventory → Create Shipment
↓ ↓ ↓ ↓
Cancel Order ← Refund Payment ← Release Inventory (compensating actions)Choreography-based saga (events):
// Each service listens and reacts
// Payment Service
on('orders.created', async (order) => {
try {
await chargePayment(order);
emit('payments.completed', { orderId: order.id });
} catch {
emit('payments.failed', { orderId: order.id });
}
});
// Inventory Service
on('payments.completed', async (event) => {
try {
await reserveItems(event.orderId);
emit('inventory.reserved', { orderId: event.orderId });
} catch {
emit('inventory.failed', { orderId: event.orderId });
// Payment service listens and refunds
}
});
// Payment Service (compensating)
on('inventory.failed', async (event) => {
await refundPayment(event.orderId);
emit('payments.refunded', { orderId: event.orderId });
});Orchestration-based saga (central coordinator):
// Saga orchestrator
class OrderSaga {
async execute(orderData: OrderInput): Promise<string> {
const steps: SagaStep[] = [
{
action: () => paymentService.charge(orderData),
compensate: (result) => paymentService.refund(result.paymentId),
},
{
action: () => inventoryService.reserve(orderData.items),
compensate: (result) => inventoryService.release(result.reservationId),
},
{
action: () => shippingService.createShipment(orderData),
compensate: (result) => shippingService.cancel(result.shipmentId),
},
];
const results: any[] = [];
for (let i = 0; i < steps.length; i++) {
try {
results.push(await steps[i].action());
} catch (error) {
// Compensate all previous steps in reverse
for (let j = i - 1; j >= 0; j--) {
await steps[j].compensate(results[j]);
}
throw new SagaFailedError(error.message);
}
}
return 'Order completed successfully';
}
}Pattern Comparison
|---------|----------|------------|---------|-----------------|
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 170" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="170" rx="12" fill="#1a1a2e"/><circle cx="60" cy="85" r="25" fill="#f59e0b" opacity="0.85"/><text x="60" y="82" text-anchor="middle" fill="#1a1a2e" font-size="9" font-family="system-ui" font-weight="bold">Trigger</text><text x="60" y="94" text-anchor="middle" fill="#1a1a2e" font-size="8" font-family="system-ui">webhook</text><polygon points="175,55 210,85 175,115 140,85" fill="#6366f1" opacity="0.85"/><text x="175" y="88" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">If</text><rect x="250" y="35" width="100" height="40" rx="6" fill="#2dd4bf" opacity="0.85"/><text x="300" y="55" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Send Email</text><text x="300" y="67" text-anchor="middle" fill="#1a1a2e" font-size="8" font-family="system-ui">SMTP</text><rect x="250" y="95" width="100" height="40" rx="6" fill="#a855f7" opacity="0.85"/><text x="300" y="115" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Log Event</text><text x="300" y="127" text-anchor="middle" fill="#ffffff" font-size="8" font-family="system-ui">database</text><rect x="400" y="55" width="100" height="40" rx="6" fill="#3b82f6" opacity="0.85"/><text x="450" y="75" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Update CRM</text><text x="450" y="87" text-anchor="middle" fill="#ffffff" font-size="8" font-family="system-ui">API call</text><circle cx="545" cy="75" r="18" fill="none" stroke="#2dd4bf" stroke-width="2"/><text x="545" y="79" text-anchor="middle" fill="#2dd4bf" font-size="9" font-family="system-ui">Done</text><defs><marker id="arrow10" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="87" y1="85" x2="138" y2="85" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="210" y1="72" x2="248" y2="55" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="210" y1="98" x2="248" y2="115" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="352" y1="55" x2="398" y2="68" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="352" y1="115" x2="398" y2="82" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><line x1="502" y1="75" x2="525" y2="75" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow10)"/><text x="225" y="45" text-anchor="middle" fill="#2dd4bf" font-size="8" font-family="system-ui">true</text><text x="225" y="120" text-anchor="middle" fill="#a855f7" font-size="8" font-family="system-ui">false</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Workflow automation: triggers, conditions, and actions chain together to eliminate manual processes.</p></div>
Choosing the Right Pattern
Does the caller need an immediate response?
├── Yes → Sync (REST/gRPC)
│ └── Can the caller tolerate failure?
│ ├── Yes → Add circuit breaker + retry
│ └── No → Consider async with response queue
└── No → Async messaging
└── Do you need ordering guarantees?
├── Yes → Event streaming (NATS JetStream, Kafka)
└── No → Simple pub/sub (NATS core, RabbitMQ)At TechSaaS, our self-hosted Docker services use synchronous HTTP calls (they are on the same Docker network, so latency is microseconds). For client architectures with true microservices, we implement async messaging with NATS for lightweight setups or Kafka for high-throughput needs, and the saga pattern for distributed transactions.
Need help with platform engineering?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.