Event-driven architecture promises loose coupling, independent scalability, and a natural audit log. It delivers all three — and then introduces a new class of problems: lost events, ordering anomalies, schema drift, and distributed debugging nightmares. The 2026 guide covers both sides honestly.
What changed in 2026
- CloudEvents 1.1 is now the standard envelope for event metadata across cloud providers (AWS EventBridge, Google Eventarc, Azure Event Grid) — a single event schema spec reduces the semantic mismatch between platforms.
- Kafka's removal of ZooKeeper dependency (KRaft mode) is production-stable — self-hosted Kafka clusters are significantly simpler to operate.
- Schema registries became table stakes — Confluent Schema Registry, AWS Glue Schema Registry, and Apicurio are all mature; teams that skip them accumulate schema debt immediately.
- Temporal.io and similar workflow engines emerged as the answer to "choreography got too complex" — they provide orchestration with durability guarantees without reverting to a monolith.
Core concepts
An event is an immutable record of something that happened: OrderPlaced, PaymentProcessed, UserDeactivated. It names the past tense, carries a payload of relevant data, and is published to a channel (topic, event bus).
A command tells a service what to do: PlaceOrder, ProcessPayment. Commands expect a response; events do not.
The distinction shapes your system: event-driven systems are push-based, asynchronous, and loosely coupled. Command-driven systems are pull-based, synchronous, and tightly coupled at the protocol level.
Choreography vs orchestration
| Aspect |
Choreography (events) |
Orchestration (commands/workflow) |
| Coupling |
Services don't know each other |
Orchestrator knows all steps |
| Scalability |
High — consumers scale independently |
Medium — orchestrator can bottleneck |
| Debuggability |
Hard — flow is implicit |
Easy — flow is explicit in one place |
| Failure handling |
Each service handles its own |
Centralised retry and compensation |
| Best for |
Simple, stable flows |
Complex, long-running transactions |
Use choreography for simple fan-out (one event → multiple independent consumers). Use orchestration (Temporal, AWS Step Functions) for multi-step workflows where you need to reason about the overall state.
The outbox pattern
The dual-write problem: you write to your database and then publish an event. If the service crashes between the two, you have an inconsistent state — the DB write happened but the event was never published.
The outbox pattern solves this:
- Write the domain change and an
outbox record in the same database transaction.
- A separate relay process (or Debezium CDC) reads the outbox table and publishes to the event broker.
- Mark the outbox record as processed after successful publish.
-- Application writes atomically
BEGIN;
INSERT INTO orders (id, customer_id, ...) VALUES (...);
INSERT INTO outbox (aggregate_id, event_type, payload, published)
VALUES (order_id, 'OrderPlaced', '{"..."}', false);
COMMIT;
-- Relay process (or Debezium CDC)
SELECT * FROM outbox WHERE published = false ORDER BY created_at LIMIT 100;
-- publish each to Kafka
-- UPDATE outbox SET published = true WHERE id = ...
Event schema evolution
Events are immutable once published. Consumers that read old events from a stream must still work when new fields are added. Rules:
- Add fields as optional with defaults — never remove or rename a field.
- Use Avro or Protobuf with a schema registry; JSON without a registry leads to undocumented schema drift.
- Version your event types (
OrderPlaced.v1, OrderPlaced.v2) for breaking changes; run both versions concurrently until all consumers migrate.
Event sourcing vs event-driven
Event-driven means services communicate via events. State is still stored normally in a database.
Event sourcing means the event log is the state — you derive current state by replaying all events. More powerful (time-travel, audit log for free), much harder to operate (projections, snapshot management, eventual consistency everywhere). Don't conflate the two or adopt event sourcing without clear need.
How to pick
- Do multiple services need to react to the same thing? → Emit events, let consumers subscribe independently.
- Do you need the reaction to complete before responding to the user? → Synchronous call or orchestrated workflow, not choreography.
- Is the flow more than 3–4 steps with compensating transactions? → Orchestration engine (Temporal, Step Functions).
- Do you need an audit log of all state changes? → Event sourcing is justified.
- Is your team small and the domain simple? → Start with a monolith and synchronous calls; EDA complexity compounds quickly.
Common mistakes
Publishing events without a schema contract. JSON with no schema registry means consumers break silently when producers change fields. Enforce schema compatibility from the first event.
Not handling idempotency. Events are delivered at-least-once. Every consumer must be idempotent.
Treating events as commands. An event says what happened — it is not a request. If PaymentProcessed causes the inventory service to reserve stock, that's modelling a dependency as an event. Name the concern correctly.
No event replay capability. If your broker doesn't retain events, you can't recover a consumer that fell behind or deploy a new consumer that needs historical data. Kafka/Redpanda retention is essential.
Choreography for complex sagas. When you have 8 services all reacting to each other's events to implement a business transaction, you need an orchestrator.
What to skip
- Event-driven for simple CRUD — if a service's state is a user record that gets updated and read, REST is simpler, more observable, and faster to build.
- Custom event buses before evaluating managed options — AWS EventBridge, Google Eventarc, and Azure Event Grid handle routing, filtering, and retry for a fraction of the operational cost of self-hosted.
- Synchronous event processing that blocks the producer — if the producer waits for the consumer, you've built a slow RPC call.
FAQ
How do I debug an event-driven system in production?
Distributed tracing (OpenTelemetry) with trace context propagated through event headers. Every event should carry traceparent/traceId so you can reconstruct the causal chain in Jaeger, Tempo, or Honeycomb.
What is the difference between an event and a message?
Events are facts about the past (immutable, named in past tense). Messages are generic payloads — commands, queries, or events. All events are messages; not all messages are events.
Should events be fine-grained or coarse-grained?
Start coarse — one event per business transaction (OrderPlaced with full order payload). Split only when consumers consistently need a subset and the payload is large.
How do I handle a consumer that's significantly behind?
Set consumer lag alerts. For Kafka, rebalance partitions or add consumer instances. If a consumer is days behind, investigate the processing bottleneck first before throwing hardware at it.
Where to go next
See Message queues explained in 2026 for the broker layer that powers event-driven systems, and Feature flags guide in 2026 for gradually rolling out new event consumers without a hard cutover.