ReviseAlgo Logo

Microservices Architecture

Communication Patterns — REST (sync) vs Events (async)

Trade-offs between HTTP API calls latency and Kafka event-driven decoupling.

Last Updated: June 14, 2026 12 min read

Introduction

Microservices must exchange data to perform work. This communication is structured using either Synchronous (Request-Response) patterns via HTTP/gRPC, where the caller waits for a reply, or Asynchronous (Event-Driven) patterns via message brokers, where the caller fires a message and releases its execution thread immediately.

Why It Matters

Relying entirely on synchronous REST queries binds the availability of your application to the weakest link in the chain. If Service A depends synchronously on Service B, and Service B goes down, Service A fails too. Asynchronous event messaging breaks this runtime dependency, allowing services to process requests even when downstream targets are offline.

Real-World Analogy

Think of **Synchronous REST** like a **telephone call**. Both you and the receiver must be active and online at the exact same moment to exchange data. If they don't answer, the conversation fails. Think of **Asynchronous Messaging** like sending an **email**. You compose the message, hit send, and continue working on other tasks. The recipient reads and processes the message whenever they are online and ready.

Detailed Mechanics

1. Cascading Failures and Thread Exhaustion

When using synchronous blocking clients, the caller's server thread remains blocked until the downstream REST service responds. If the downstream service encounters slow performance, calling threads pool up. This causes **latency amplification** and eventual **thread pool exhaustion** at the gateway, crashing unrelated service routes.

2. Eventual Consistency and BASE

Monolithic databases offer strong ACID transactions. In microservices, transactions span network boundaries. Asynchronous architectures use the **BASE** model (Basically Available, Soft State, Eventual Consistency). Instead of blocking database operations, services execute local updates and publish events. Downstream listeners process those events asynchronously, bringing the system into sync over time.

Trade-offs Matrix

Metric Synchronous REST (HTTP/gRPC) Asynchronous Events (Kafka/RabbitMQ)
Caller Threading Blocked (pinned waiting for request return) Non-blocking (released immediately after dispatch)
Temporal Coupling Tightly coupled (both services must be online) Decoupled (receiver can process messages later)
Complexity Low (Standard programming model, easy trace debugging) High (Requires retry setups, idempotency, event stores)

Quick Quiz

Q1: Which pattern describes the phenomenon where one slow service causes threads to pool up and fail upstream services in a chain?

A) Load balancing split

B) Cascading failure

C) Database isolation block

D) Event buffer lag

Answer: B — Cascading failures happen when thread resources block upstream, taking down adjacent systems.

Q2: Why is the BASE consistency model used instead of strict ACID inside asynchronous event-driven microservices?

A) Because ACID does not support SQL databases.

B) Distributed transactions over multiple networks are slow and prone to deadlocks; BASE relies on eventual consistency to keep systems scalable.

C) ACID is deprecated in Spring Boot 3.

D) BASE stores all data inside JVM memory pools automatically.

Answer: B — Enforcing ACID across network calls compromises availability and system performance; eventual consistency offers high scalability.

Scenario-Based Challenge

Production Scenario:

Your checkout system is designed synchronously: when a user clicks "Buy", the Order Service calls the Payment Service, then calls the Inventory Service, and finally calls the Notification Service (to send an email). During a promotional event, the email server crashes, causing checkout requests to time out. Users click "Buy" multiple times, resulting in duplicate charges. How do you redesign this architecture?

View Solution

Decouple non-essential tasks from the primary request thread using asynchronous events:

  1. Keep payment validation synchronous: have the Order Service execute payment validation and confirm the order immediately to the customer.
  2. Upon order confirmation, publish an OrderPlacedEvent to a message queue (like Kafka).
  3. Have the Inventory Service and Notification Service subscribe to this event. If the email server goes down, the event sits securely in the queue. The Notification Service will consume and process it once it recovers, without blocking checkout.
  4. Ensure consumers implement **idempotency** (checking a unique order ID database key before processing) to prevent duplicates if messages are retried.

Interview Questions

1. Conceptual: What is the difference between a Command and an Event in messaging systems?

A **Command** represents an instruction to trigger an action in the future (e.g. ProcessPaymentCommand). It has a single designated recipient. An **Event** represents a historical fact of something that has already occurred in the past (e.g. PaymentProcessedEvent). It is broadcast to any interested service and cannot be rejected.

2. Concept: How can you implement the Outbox Pattern to ensure reliable event publishing?

Instead of writing directly to a database and then publishing to a message broker, save both your domain entity and the outgoing event object in the local database within a single local transaction. A separate background processor (using polling or Debezium Change Data Capture) continuously reads from the outbox table and publishes to the broker. This prevents data updates without corresponding events if the broker crashes.

Production Considerations

When designing event-driven microservices, always assume messages can be delivered more than once (At-least-once delivery). Build **idempotent consumers** that check whether they have already processed a given transaction ID before applying state modifications to prevent data corruption.