DEV Community

Cover image for Event-driven vs request-driven architecture
binadit
binadit

Posted on • Originally published at binadit.com

Event-driven vs request-driven architecture

When synchronous chains kill your application performance

Your app handles 10 users perfectly. At 100 users, everything crawls. At 500? Complete system failure.

I've watched countless teams hit this wall. Their request-driven architecture works flawlessly in development, then crumbles under production load. Here's why this happens and how to fix it.

The synchronous bottleneck problem

In request-driven systems, every user action triggers a chain of blocking operations:

User submits order →
  Validate payment (300ms) →
  Check inventory (200ms) →
  Send confirmation email (400ms) →
  Update analytics (150ms) →
  Return response (total: 1050ms)
Enter fullscreen mode Exit fullscreen mode

One slow step kills everything downstream. When your email service takes 3 seconds instead of 400ms, users wait 3.75 seconds for a simple order confirmation.

Under load, this gets exponentially worse. With 200 concurrent orders, you need 200 simultaneous connections to every downstream service. Resources exhaust quickly. Requests pile up. The cascade failure begins.

Event-driven architecture as a solution

Event-driven systems break these synchronous chains by making operations asynchronous:

User submits order →
  Validate payment (300ms) →
  Check inventory (200ms) →
  Publish "order_placed" event →
  Return response (total: 500ms)

Meanwhile, independent services handle:
  Email service → sends confirmation
  Analytics service → records sale
  Fulfillment service → prepares shipment
Enter fullscreen mode Exit fullscreen mode

Users get immediate feedback. Background services process at their own pace. One slow service doesn't block others.

How most teams mess this up

Creating event spam

// Wrong: too granular
emit('user_email_updated', { userId, newEmail })
emit('user_phone_updated', { userId, newPhone })
emit('user_name_updated', { userId, newName })

// Right: business-focused
emit('user_profile_updated', { userId, changes })
Enter fullscreen mode Exit fullscreen mode

Bloated event payloads

// Wrong: kitchen sink approach
emit('order_placed', {
  order: fullOrderObject,
  user: fullUserObject,
  products: allProductDetails,
  analytics: trackingData
})

// Right: minimal context
emit('order_placed', {
  orderId: '12345',
  userId: '67890',
  timestamp: Date.now()
})
Enter fullscreen mode Exit fullscreen mode

Synchronous event processing

// Wrong: defeats the purpose
const result = await publishAndWait('order_placed', orderData)
return result

// Right: fire and forget
publish('order_placed', orderData)
return { success: true, orderId }
Enter fullscreen mode Exit fullscreen mode

The hybrid approach that actually works

Don't go full event-driven immediately. Start with a hybrid pattern:

Critical path stays synchronous:

  • Payment validation
  • Inventory checks
  • Core business logic

Non-critical operations become asynchronous:

  • Email notifications
  • Analytics updates
  • Audit logging
  • Third-party integrations

This gives you immediate performance gains without the complexity of full event-driven architecture.

When to use each approach

Stick with request-driven for:

  • Simple CRUD applications
  • Small teams (2-5 developers)
  • Workflows requiring immediate consistency
  • Systems with predictable, moderate load

Move to event-driven for:

  • High-volume applications
  • Complex workflows with many independent steps
  • Multi-service architectures
  • Systems requiring audit trails
  • When different components scale at different rates

Real performance impact

A recent client saw these improvements after implementing hybrid architecture:

  • Response times: 800ms → 150ms
  • Concurrent users: 200 → 2000+
  • System availability: 94% → 99.2%
  • Development velocity: significantly faster (independent service deployments)

Key implementation tips

  1. Start small: Convert one non-critical workflow to events first
  2. Handle duplicates: Events can be delivered multiple times
  3. Include event ordering: Use timestamps and sequence numbers
  4. Monitor event lag: Track processing delays
  5. Design for failure: Events can be lost or delayed

The bottom line

Request-driven architecture isn't wrong, it's limited. When synchronous chains become your bottleneck, events offer a path to better performance and scalability.

But don't rearchitect everything overnight. Start with hybrid patterns. Move non-critical operations to events. Keep core workflows synchronous until you understand event-driven complexities.

Your users will thank you for the faster response times, and your team will appreciate the improved system resilience.

Originally published on binadit.com

Top comments (0)