Event-Driven#

The natural fit for an operator’s collection-and-analysis pipeline. recon hits, sensor alerts, and capture events get published to a broker; enrichment, scoring, indexing, and audit consume them independently. Components communicate by publishing and subscribing to events on a broker instead of calling each other directly. The producer doesn’t know who consumes; consumers don’t block the producer; new analytics can be added mid-campaign without touching upstream collection.

        flowchart LR
    subgraph P[Producers]
        order[Order]
        payment[Payment]
        shipping[Shipping]
    end

    broker[(Event Broker)]

    subgraph C[Consumers]
        email[Email]
        analytics[Analytics]
        search[Search Indexer]
        audit[Audit Log]
    end

    subgraph S[Stores]
        emailDb[(Email Provider)]
        warehouse[(Warehouse)]
        searchDb[(Search Index)]
        logs[(Append-Only Log)]
    end

    order --> broker
    payment --> broker
    shipping --> broker

    broker --> email
    broker --> analytics
    broker --> search
    broker --> audit

    email --> emailDb
    analytics --> warehouse
    search --> searchDb
    audit --> logs
    

Structure#

The three roles in any event-driven system. The producer doesn’t know who consumes; consumers can come and go without the producer noticing. The broker is what decouples them, and becomes the system’s most important critical dependency.

  • Producer publishes events describing what happened (past tense).

  • Broker stores or routes events.

  • Consumer subscribes to events relevant to it.

  • No request/response between producer and consumer; the producer doesn’t wait.

Event vs. Command#

The distinction that separates event-driven from request/response. Modeling things as events instead of commands is the central shift; OrderShipped (event) lets many consumers react; ShipOrder (command) implies a single target.

  • Command, “do X”. Imperative. Caller expects an action and usually a result. Synchronous makes sense.

  • Event, “X happened”. Past tense. Many subscribers may react. Async by nature.

Modeling things as events instead of commands is the central shift. OrderShipped is an event; ShipOrder is a command.

Brokers#

The four broker types serve different consumer models. Pick by replay needs, ordering guarantees, retention, and how many independent consumers will subscribe; the choice rarely changes after launch.

  • Queues (SQS, RabbitMQ), one consumer per message; load leveling between producers and consumers. Messages disappear once consumed.

  • Logs / streams (Kafka, Kinesis, Pulsar), replayable; many independent consumers each track their own position. Messages persist.

  • Pub/sub (NATS, MQTT, Cloud Pub/Sub), broadcast to subscribers; no replay.

  • Event buses (EventBridge, Azure Event Grid), routing layer with filters and rules.

Pick by replay needs, ordering guarantees, retention, and consumer model.

Strengths#

What event-driven buys that synchronous request/response can’t match. Loose coupling and fan-out are the structural wins; buffering and replay are the operational wins; the organizational scaling is the under-counted reason teams adopt the pattern.

  • Loose coupling, producers and consumers evolve independently.

  • Broadcast / fan-out, new consumers join without changing producers.

  • Buffering, consumers can be slower than producers without blocking.

  • Replay, log-based brokers let you rebuild state or backfill new consumers.

  • Async-first scales further organizationally; teams don’t need to call each other to build features.

Costs#

The bill that comes with the loose coupling. Eventual consistency, broker dependency, awkward request/response, mandatory idempotency, harder debugging, and ongoing schema evolution all become standing concerns rather than implementation details.

  • Eventual consistency by default, the world catches up to the event over time.

  • Broker is a critical dependency, if it goes down, everything pauses.

  • Harder request/response semantics, if a consumer needs to send a result back, you’re inventing correlation IDs and reply queues.

  • Idempotency required, “exactly once” is a marketing term; design consumers to handle duplicates.

  • Debugging is harder, the call stack is gone; you trace via event IDs.

  • Schema evolution matters, producers and consumers drift; use a schema registry.

Patterns#

The four patterns that come up most often in production event-driven systems. Event sourcing for audit-perfect history, CQRS for write/read divergence, sagas for multi-service workflows, outbox for atomic write+publish.

Event sourcing

Persist the sequence of events that changed state, not just the current state. Current state is derived by folding events.

  • Strong audit trail by construction.

  • Easy to add new projections after the fact.

  • Hard parts: schema evolution of events, snapshotting for performance, cross-aggregate consistency.

CQRS (Command Query Responsibility Segregation)

  • Writes through validated command handlers against a normalized model.

  • Reads come from one or more denormalized projections.

  • The two sides communicate via events.

Often paired with event sourcing.

Saga

A long-running business process that spans multiple services.

  • Choreography, each service reacts to events and emits new ones; no central coordinator. Simple at small scale; hard to follow at large.

  • Orchestration, a coordinator drives the steps and handles failures. Easier to reason about, but the coordinator is a critical service.

Compensation is rarely a perfect inverse; build for eventual consistency.

Outbox

  • Write business state and an “outbox” event in the same DB transaction.

  • A separate process publishes events from the outbox to the broker.

  • Solves “at least once” delivery without distributed transactions.

Schemas#

Producer/consumer schemas drift independently in any long-running system. Manage that with a schema registry, explicit compatibility rules, contract tests in CI, and tolerant readers that skip unknown fields rather than failing on them.

  • Schema registry for events (Avro, Protobuf, JSON Schema).

  • Compatibility rules, backward, forward, full.

  • Consumer-driven contract tests to catch drift in CI.

  • Tolerant readers, ignore unknown fields, don’t fail on additions.

Idempotency#

Build handlers that are safe to call twice. “Exactly once” is not deliverable; “at least once” plus idempotent consumers is the real engineering target. Three approaches.

  • Each event has a stable ID; consumers dedupe on it.

  • Update operations are state transitions, not deltas.

  • When deletion + recreation is possible, store a tombstone.

Ordering#

What guarantees the broker actually offers vs what the application assumes. Per-partition ordering is the standard; global ordering is rare and expensive; partition-key choice is the lever that makes ordering work for your specific correlations (per-tenant, per-order, etc.).

  • Per-partition ordering is the typical guarantee (Kafka).

  • Global ordering is rare and expensive.

  • Pick partition keys carefully; usually a tenant or aggregate ID.

When to Pick Event-Driven#

The fits where event-driven pays off. Most are about fan-out (many consumers per event) or async tolerance (no synchronous answer needed) or audit needs (the log itself is a feature).

  • Many consumers want to react to the same fact.

  • Producer doesn’t care who’s listening (“user signed up” → analytics, CRM, welcome email, fraud check).

  • Flow doesn’t need a synchronous answer.

  • Throughput is high enough that buffering matters.

  • Audit trail is required, log-based event storage gives it for free.

When to Skip#

The cases where event-driven is the wrong fit. Synchronous business flows, low-volume CRUD, missing operational capacity, and required strong consistency all push toward request/response.

  • The business flow really does need a synchronous answer right now (payment authorization).

  • Low-volume CRUD; the operational complexity of a broker doesn’t pay off.

  • No engineering capacity to operate the broker.

  • Strong consistency is required across the affected components.

Common Pitfalls#

The five ways event-driven systems fail in production. Each one is a violation of the basic event-driven invariants; producer doesn’t know consumers, schemas evolve gracefully, ordering assumptions match the broker, sagas are coordinated.

  • Treating events as commands by another name (one consumer, one producer, one type per event).

  • Skipping schema evolution; breaking consumers on every change.

  • No replay or retry strategy; messages get lost on transient failures.

  • Tight ordering assumptions where the broker doesn’t guarantee them.

  • Building the saga in 50 places instead of one orchestrator.