Skip to content

Event-driven architecture

In an event-driven architecture, components announce that something happened (“order placed”, “payment failed”) by publishing an event. Other components subscribe to the events they care about and react in their own time. The producer doesn’t know, or wait for, its consumers.

This loose coupling lets new behavior be added by adding a subscriber, without changing the code that raised the event.

flowchart LR
    checkout[Checkout] -- OrderPlaced --> broker{{Event broker}}
    broker --> billing[Billing]
    broker --> warehouse[Warehouse]
    broker --> email[Email]
    broker --> analytics[Analytics]
    billing -- PaymentCaptured --> broker
  • Events are facts. An event records something that has already happened and is named in the past tense. It isn’t a request for someone to do something.
  • A broker delivers events. Message brokers (such as RabbitMQ) or event-streaming platforms (such as Apache Kafka) store events and deliver them to subscribers.
  • Two common styles of event:
    • Event notification: a small message saying something changed. Consumers call back for details if they need them.
    • Event-carried state transfer: the event carries the data consumers need, so they never call back.
  • Choreography or orchestration. Services can react to each other’s events with no central controller (choreography), or a coordinator can drive the steps of a process (orchestration).
  • Loose coupling: producers and consumers evolve independently.
  • Easy extension: add a new consumer without touching the producer.
  • Resilience to spikes: the broker buffers load, so slow consumers catch up instead of failing.
  • Natural audit trail: a stream of events records what happened and when.
  • Harder to follow: the overall flow is spread across many subscribers, so no single place shows what happens after an event.
  • Eventual consistency: consumers see changes after a delay, which users and downstream systems must tolerate.
  • Duplicate and out-of-order delivery: most brokers guarantee at-least-once delivery, so consumers must be idempotent.
  • Schema evolution: changing an event’s shape can silently break consumers you didn’t know existed.
  • Many systems need to react to the same business occurrence.
  • Work can happen asynchronously, after the user has had a response.
  • Load is spiky and you need to absorb bursts.
  • You need an audit trail or the ability to replay history.
  • The caller needs an immediate, consistent answer (e.g. checking an account balance before approving a withdrawal).
  • A simple workflow with two or three steps that a direct call would handle clearly.
  • The team has no tooling yet to trace events across services.
  • Commands disguised as events. “SendWelcomeEmail” is a command; “CustomerRegistered” is an event. Mixing them reintroduces coupling.
  • Lost events between the database and the broker. Writing to the database and publishing an event aren’t atomic. Use the transactional outbox pattern.
  • No schema governance. Use a schema registry and versioning rules from the start.
  • Unbounded retries. Send repeatedly failing messages to a dead-letter queue instead of retrying forever.
  • Microservices
  • Transactional outbox
  • CQRS and event sourcing
  • Saga
  • Dead-letter queue

Glossary: eventual consistency, idempotency, at-least-once delivery

  • Martin Fowler, “What do you mean by ‘Event-Driven’?” (martinfowler.com)
  • Gregor Hohpe and Bobby Woolf, Enterprise Integration Patterns

Written and edited by Abhinav Taduka, enterprise software architect.