Event-Driven vs Request-Driven Architecture
Architecture Diagram
The Core Trade-off
Request-driven (synchronous): Service A calls Service B and waits for a response. Simple, immediate, easy to debug. The downside is runtime coupling. If B goes down, A fails too.
Event-driven (asynchronous): Service A publishes an event. Service B (and maybe C, D, E) consumes it independently. This gives you decoupling, extensibility, and resilience to downstream failures. But it introduces eventual consistency, makes debugging harder, and requires infrastructure like message brokers, schema registries, and dead-letter queues.
Neither approach is universally better. The right choice depends on your consistency requirements, your team's experience, and the specific interaction pattern you are dealing with.
When to Use Request-Driven
Go with synchronous request-response when:
- The caller needs an immediate answer to move forward (e.g., "Is this user authorized?" or "What is the current price?")
- The interaction is a query that does not change state
- Strong consistency is required, meaning the caller must know the operation succeeded before continuing
- The system is simple enough that the operational overhead of message brokers is not worth it
- Your team has limited distributed systems experience. Start synchronous and evolve toward events where it makes sense
When to Use Event-Driven
Go with asynchronous events when:
- The producer should not care who consumes the event or what they do with it (e.g., "OrderPlaced" triggers inventory, notifications, analytics, and fraud detection independently)
- The operation can tolerate seconds or minutes of delay before downstream effects kick in
- You need fan-out, where one event triggers multiple independent reactions across different services
- Resilience matters more than immediacy. If the consumer is temporarily down, events queue up and get processed when it recovers
- You want to extend the system without touching the producer. New consumers can subscribe to existing events without the producer knowing about them
The Hybrid Approach
Most production systems use both patterns. A common design looks like this:
- Synchronous for the hot path. The user submits an order. The order service validates it and writes to the database synchronously. The user gets an immediate confirmation.
- Events for side effects. After the order is persisted, the order service publishes an
OrderPlacedevent. Downstream services (inventory, shipping, notifications, analytics) consume this event asynchronously. - Synchronous for queries. When the user checks order status, the query hits a read model directly. No events involved.
This hybrid gives you immediate consistency where users expect it and decoupled extensibility for backend coordination.
Debugging Event-Driven Systems
Event-driven architectures are harder to debug because there is no call stack. Here are the practices that make a real difference:
- Correlation IDs. Every event carries a correlation ID that traces back to the original request. Log it at every step.
- Event catalog. Keep a registry of all event types, their schemas, their producers, and their consumers. Without one, nobody knows what events actually exist in the system.
- Dead-letter queues. Events that fail processing after N retries land in a DLQ for manual inspection. Never silently drop events.
- Event replay. Design consumers to be idempotent so you can replay events from a specific offset to recover from bugs.
Schema Evolution
Events are contracts. Treat them with the same care as API endpoints:
- Use a schema registry (Confluent Schema Registry, AWS Glue) to enforce compatibility.
- Follow backward-compatible evolution. Add optional fields, but never remove or rename existing ones.
- Version your events (
OrderPlaced.v1,OrderPlaced.v2) when breaking changes cannot be avoided. - Consumers must handle unknown fields gracefully. Ignore what you do not recognize.
Key Points
- •Request-driven (synchronous) is simpler to reason about and debug. Choose it as the default unless you have a concrete reason to go with events
- •Event-driven architecture works best when producers should not know about consumers. It enables real service independence and extensibility
- •Eventual consistency is the price you pay for event-driven decoupling. Make sure your domain can actually tolerate it before you commit
- •Hybrid architectures are what most production systems look like. Use synchronous calls for queries and commands that need immediate consistency, events for side effects and cross-domain coordination
- •Event schemas are your API contract. Invest in schema registries and backward-compatible evolution from day one
Common Mistakes
- ✗Using events for everything, including simple request-response flows. Events add latency, complexity, and debugging difficulty for no benefit when a synchronous call would do the job
- ✗Not defining event ownership. Every event type must have exactly one producing service that owns its schema
- ✗Building event chains that create implicit coupling. If Service A emits event X which triggers Service B which emits event Y which triggers Service C, you have a distributed monolith with extra steps
- ✗Ignoring poison pill messages. A single malformed event can block an entire consumer partition if you do not have dead-letter queue handling