Blog

Event Driven vs REST in Microservice Architecture

Key Takeaways

  • Use synchronous APIs when callers need an answer before proceeding; use asynchronous messaging when work can continue independently.
  • REST is an architectural style, not a transport or a synonym for synchronous communication.
  • Messaging shifts failure handling into publication, delivery, retries, and consumer processing—it doesn't eliminate it.
  • A hybrid design often combines HTTP APIs, durable messaging, idempotent consumers, and a transactional outbox.

Use REST-style HTTP APIs for immediate request/response interactions and asynchronous events for decoupled updates and workflows. Neither is universally better: the useful boundary is what the caller must know before it continues, and what can happen later. These patterns can coexist within the same service.

What Is a Microservice?

A microservice is an independently deployable service organized around a business capability, with ownership of its behavior and data. Splitting an application into a UI, service layer, and shared database doesn't automatically create microservices.

Consider a blog. Accounts, publishing, and comments could become separate services if they need independent deployment or scaling. Each service would control access to its own data; the UI would usually remain a client. This is a proposed decomposition, not a requirement to split every blog.

Microservices introduce network failures, cross-service consistency problems, and operational overhead. A monolith doesn't inevitably fail as a unit, and a microservice outage can affect dependent services. Sound boundaries and deliberate fault containment matter more than the deployment count. Before distributing the application, consider the tradeoffs of microservices versus a modular monolith.

What Is Event-Driven Architecture?

Event-driven architecture connects producers that announce changes with consumers that react to them. An event channel—such as a message broker or retained event log—carries those announcements. Across microservices, this commonly uses asynchronous messaging: the producer needn't wait for downstream business processing to finish.

Commands and Events Are Different Messages

A command requests work, such as ReserveInventory. An event reports a fact, such as InventoryReserved. Commands have an intended handler; events can interest multiple independent consumers. Both can travel through messaging infrastructure, so not every queued message is an event.

Publish/subscribe delivers an announcement to interested subscribers; competing workers can instead share jobs. Choose the interaction first, then the infrastructure. Our JMS versus Kafka comparison helps separate messaging APIs from platform choices.

What Is REST?

Representational State Transfer, or REST, is an architectural style commonly implemented over HTTP. Its constraints include client/server separation, stateless interactions, cache constraints, a uniform interface, a layered system, and optional code-on-demand. Stateless means requests carry the context needed to understand them—not that services have no stored data.

The uniform interface includes resource identification, representations, self-describing messages, and hypermedia: links that guide available next actions. An API isn't necessarily RESTful just because it sends JSON using GET, POST, PUT, and DELETE. See Fielding's REST constraints for the original definition.

Proxies and caches participate in a layered system and can affect responses. Cache reuse follows HTTP caching rules and headers such as Cache-Control; some responses also permit heuristic caching.

HTTP can also start asynchronous work. A 202 Accepted response means a request was accepted for processing, not that processing finished or will necessarily succeed. Synchronous communication and REST are separate choices.

Event-Driven vs REST Microservices: Practical Tradeoffs

The comparison below contrasts typical synchronous HTTP calls with asynchronous messaging—not every possible REST or messaging implementation. Brokers can also support request/reply interactions.

ConcernSynchronous HTTP APIAsynchronous messaging
Style and transportREST describes an interface; HTTP carries requests and responses.Event-driven design describes reactions to facts; brokers, logs, or other channels carry events.
Caller couplingThe caller waits for a response or timeout.The producer can continue after the required publication confirmation.
LatencyCan return a business result in one exchange; dependency chains add latency.Can acknowledge submission quickly, but completion includes queueing and processing time.
Throughput and backpressureConcurrency limits and admission control protect dependencies.Buffering absorbs bursts; sustained overload still needs capacity or admission control.
Fan-out and replayMultiple recipients require explicit coordination; replay isn't supplied by HTTP.Subscriptions support fan-out; retained logs support replay while records remain available.
Failure isolationA slow dependency can exhaust caller resources without safeguards.A consumer outage can become a backlog rather than an immediate producer failure.
Operational costRequires dependency management, deadlines, and retry policies.Adds broker operation, retention, consumer progress, and recovery procedures.

These are architectural tradeoffs, not performance rankings. Queue-based load leveling explains buffering; it doesn't make downstream capacity unlimited.

Synchronous request                 Asynchronous event
Caller --request--> Service         Producer --event--> Broker
Caller <--result/error-- Service     Producer <--confirmation-- Broker
                                     Broker --delivery--> Consumer
Timeout may leave outcome unknown.   Consumer --progress--> Broker
Retry only with a safe policy.       Processing may require retry.
                                     Confirmation != business completion.

Choose the Pattern Per Interaction

  • Read a resource: use an HTTP API when the caller needs the result now.
  • Check credentials or eligibility: use request/response when the next action depends on the answer. That requirement doesn't mandate REST.
  • Generate a report: accept a command, process it in the background, and expose job status.
  • React to an order: publish an event so notifications, fulfillment, and analytics can react independently.
  • Analyze a stream: consider retained events when consumers need history, replay, or windowed aggregation.

For a hypothetical ridesharing system, trip queries could use HTTP while location updates feed matching and analytics consumers. Define an explicit freshness target: asynchronous messaging alone doesn't establish a real-time deadline.

Where Kafka Fits

Kafka supports durable, partitioned event streams and rereading retained records. It's worth considering for replay, independent consumers, and high-volume data pipelines. A dedicated task queue may be simpler when your main requirement is routed jobs with independent acknowledgments, rather than a retained stream. Compare retention, ordering, recovery, and operational requirements—not Kafka against REST as interchangeable products. See when to use Kafka.

Delivery Guarantees and Consumer Failures

Publication Confirmation Isn't Consumer Completion

RabbitMQ distinguishes publisher confirms from consumer acknowledgments: the former cover publication to the broker, while the latter report consumer handling of a delivery. Kafka consumers track progress through committed offsets. Neither a broker confirmation nor an offset automatically proves that every downstream business effect completed correctly.

“Fire and forget” isn't a reliability policy. Publication confirmation, persistence, replication, retention, and consumer recovery must match the workload's requirements. RabbitMQ's reliability guide explains why applications share responsibility with the broker.

Expect Duplicates Under At-Least-Once Delivery

  • At-most-once: delivery can be lost, but the delivery mechanism doesn't redeliver it.
  • At-least-once: redelivery is allowed, so consumers must tolerate duplicate deliveries.
  • Exactly-once processing: applies within a defined transaction and failure boundary, not automatically to arbitrary external effects.

For example, Kafka transactions can coordinate consumed offsets and writes to Kafka topics. They don't automatically make an external payment or database update exactly once.

An idempotent consumer produces the same intended effect when an event is delivered again. One approach atomically records an event ID and applies database changes in the same local transaction:

# Pseudocode: database-backed consumer, not a broker API.
on delivery(event):
    begin local database transaction
        insert (consumer_name, event.id) into inbox
            only if absent, enforced by a unique constraint
        if inserted:
            apply business database changes
    commit transaction
    acknowledge delivery or record completed consumer progress

on transaction failure:
    roll back; leave progress unadvanced; use bounded retry

If the process crashes after commit but before acknowledgment, redelivery finds the recorded ID and skips the database effect. This protects only changes inside that transaction; external side effects need their own idempotency strategy. With parallel Kafka processing, don't commit a partition offset past unfinished records.

Define Ordering and Failed-Message Handling

Kafka orders records within a partition, not globally across a topic. Consumer parallelism and retry routing can also change the order in which business effects finish. Define the ordering scope you actually need, such as updates to one order.

Retry transient failures with bounded attempts and backoff. Quarantine repeatedly failing messages—often called poison messages—in a dead-letter destination for investigation and controlled redrive. Decide whether skipping a failed record is acceptable before enabling that policy; it can break workflow ordering. See RabbitMQ dead-letter exchanges for one implementation.

Combining REST APIs and Events Reliably

Use a Transactional Outbox for Publication

Suppose an order service writes an order and then publishes OrderPlaced. A crash between those actions leaves an order with no event. Publishing first creates the opposite risk: consumers receive an event for a database change that never commits. These separate writes aren't automatically atomic.

The transactional outbox pattern records the business change and an event row in one database transaction. A separate relay publishes committed outbox records, using polling or change data capture. Our change data capture explanation covers the latter mechanism.

Client --HTTP request--> Order service
                              |
                              v
                   One local DB transaction
                   +------------------------+
                   | Save order             |
                   | Save outbox event      |
                   +------------------------+
                              |
Client <--response after commit+

Committed outbox --relay--> Broker --> Fulfillment
                                  +--> Notifications
                                  +--> Analytics

For a polling relay, mark an outbox row as published only after the required broker confirmation. A crash after publication but before that mark can cause duplicate publication, so consumers still need idempotency. The outbox closes the database/publication gap; it doesn't create a transaction across all consumers.

Model Consistency and Multiservice Workflows Explicitly

An order can be committed while downstream inventory or reporting views still lag. This is eventual consistency: services may temporarily disagree while updates propagate. A synchronous response doesn't make separate service databases one atomic transaction either.

A saga coordinates local transactions across services, through event choreography or an orchestrator. If payment fails after inventory reservation, a compensating action might release the reservation. Compensation is a business action, not an automatic database rollback across services. Keep strict invariants within one ownership boundary where practical, and expose pending or failed workflow states honestly.

Contracts and Production Safeguards

Evolve APIs and Events Without Breaking Consumers

Both styles depend on contracts. Give each event schema an owner, document its meaning, and test compatibility with existing consumers—including consumers replaying older records. Additive fields are compatible only when readers and schema rules tolerate them; changing a field's meaning can break consumers without changing its type.

Apply the same discipline to HTTP API evolution: preserve compatible behavior and plan migrations for breaking changes. Independently deployed services shouldn't require a coordinated release for every contract adjustment.

Production Checklist

  • Bound synchronous work: set connection and request timeouts, an overall deadline, and retry budgets. Use circuit breakers to limit calls to failing dependencies.
  • Make retries safe: a timeout doesn't prove that a write failed. Use idempotency for repeatable commands and backoff with jitter for retryable failures.
  • Bound asynchronous work: define retention, backlog limits, consumer concurrency, and a recovery plan. A queue postpones overload; it doesn't remove it.
  • Trace the workflow: propagate trace context and correlation IDs across HTTP and message boundaries. Measure acceptance-to-completion latency, consumer lag, queue depth, and failed-message counts.
  • Protect data: restrict access to event channels and avoid distributing secrets or unnecessary personal information in retained events.
  • Exercise recovery: test duplicate delivery, dependency outages, replay, incompatible payloads, and crashes around transaction boundaries.

OpenTelemetry context propagation connects distributed telemetry. Design reliability around the complete business operation, not merely a successful HTTP response or an available broker.

FAQ

When should microservices communicate over REST instead of events?

Use REST-style HTTP APIs for resource reads and operations whose result the caller needs before proceeding. The need for an immediate response favors synchronous communication, but doesn't require REST specifically.

Can a microservices architecture use both REST APIs and event-driven messaging?

Yes. A service can accept an HTTP request, commit its state and an outbox record together, and let downstream consumers process the resulting event independently. Make clear whether the response means accepted, locally committed, or fully completed.

Are events more reliable than REST API calls?

Neither is inherently more reliable. Durable messaging can preserve work during consumer outages, but both approaches need explicit policies for persistence, timeouts, retries, idempotency, and recovery.

How do you handle duplicate messages in event-driven microservices?

Make consumers idempotent, for example by recording a unique event ID and applying its database changes in one transaction. Acknowledge or advance completed progress afterward; external side effects need their own duplicate protection.

Is Kafka a replacement for REST APIs?

No. Kafka provides durable event streams, partition-level ordering, and replay of retained records; REST-style APIs provide resource-oriented interactions commonly carried over HTTP. Choose Kafka for stream requirements, not merely to avoid HTTP calls.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. Author of Mastering the Tech Interview. More about me