By 2026, event-driven architecture (EDA) is no longer a differentiator: it is the baseline. Companies operating at any meaningful scale publish domain events to a durable broker, services communicate asynchronously, and the monolith has been decomposed into services that emit events when their state changes. The conversation has matured. We no longer ask "Kafka or RabbitMQ?" but what problem are we solving: work distribution or event history? That distinction matters more than product names.
The distinction that changes everything: work vs. history
A task says "do this". An event says "this happened". It sounds subtle, but it shapes the entire architecture. OrderPlaced, PaymentCaptured, or InventoryReserved are domain facts; "send the confirmation email" is an instruction.
- Queues: optimized for point-to-point processing. A worker picks up the message, processes it, and acknowledges it. Great for background jobs, load leveling, delayed retries, and smoothing traffic spikes.
- Streams: a durable append-only log. Ideal for replay, multiple independent consumers, state reconstruction, auditability, and real-time analytics.
If your main concern is "take this work and do it once", you want a queue. If multiple services need to read the same fact, or you need to rebuild state from history, you want a stream.
Rule of thumb: use a queue when you need someone to do something once; use a stream when you need to know what happened and let multiple consumers find out.
Queues: the reliable workhorse
Queues absorb bursts, let consumers scale independently, and make backpressure manageable: if the downstream service slows down, the queue buffers the pressure instead of forcing the whole system to synchronize and panic in unison. They remain the right tool for:
- Background jobs and commands processed exactly once.
- Load leveling and smoothing seasonal peaks.
- Retries with backoff and scheduled delay.
- Decoupling producer and worker with controlled backpressure.
But queues are not magic: they bring at-least-once delivery, duplicate messages, visibility timeout concerns, and ordering limitations. They do not remove the need for defensive code: they just move the complexity into the consumer.
Streams: the memory of your system
A stream is not "a fancier queue": it is an opinionated ledger that remembers everything. That is its superpower: recompute projections, feed analytics pipelines, add new consumers without touching producers, and recover from bugs by replaying events. It demands discipline:
- Partitions affect ordering; global ordering is an expensive trap.
- Retention policies must be understood and sized, not left at defaults.
- Consumers must track offsets and be replay-safe.
If you try to keep every event strictly ordered across the whole system, you turn a scalable architecture into a very expensive queue with a philosophy degree.
Idempotency: your best friend in a duplicate world
In production, at-least-once is the default contract: messages may be delivered more than once, consumers may crash after processing but before acknowledging, and retries create duplicates. The real goal is effectively-once processing, and that is an application design decision, not a broker feature.
-- Dedupe store: unique-constraint table
CREATE TABLE processed_events (
event_id UUID PRIMARY KEY,
payload JSONB,
processed_at TIMESTAMPTZ DEFAULT now()
);
-- Every write path goes through here:
INSERT INTO processed_events (event_id, payload)
VALUES ($1, $2)
ON CONFLICT (event_id) DO NOTHING;
The pattern works identically with Redis + TTL, a transactional outbox/inbox, or the table above: make duplicates boring. An idempotent, deduplicating, replay-safe consumer turns retries into non-events.
Schema registry: the contract between producer and consumer
If the schema disagrees with the data being serialized, the schema registry throws an exception and prevents malformed data from being written into the topic. Producers and consumers fetch the schema from the registry to deserialize, and compatibility rules (backward, forward, full) let contracts evolve without breaking anyone. Avro and Protobuf are the standard; the schema registry is what makes "add a field" a safe change instead of an incident.
The hybrid pattern and the streaming SQL layer
Mature systems in 2026 do not pick one: they combine both. Queues for commands and jobs; streams for events and history. For example, checkout emits OrderPlaced into a stream; inventory, billing, shipping, and analytics consume it independently; a separate queue handles emails, PDFs, and other task-oriented work.
Checkout → Stream (domain event)
├── inventory (independent consumer)
├── billing (independent consumer)
├── shipping (independent consumer)
└── analytics (independent consumer)
Email / PDF / notification → Queue (work, 1 consumer)
The 2026 addition: a SQL-based stream processing layer (Flink, streaming database platforms) sits on top of the stream and maintains continuously updated materialized views. The broker answers "what events happened"; the materialized view answers "what is the current state of X".
Materialized views and AI agents (the new 2026 addition)
An agent querying the topic directly receives a raw log and must rebuild state from it — expensive and slow. An agent querying the streaming layer gets pre-computed, always-fresh state: order status, active SLA violations, real-time inventory. In 2026, agents connect via MCP to these views instead of parsing logs, turning real-time observability into something natively queryable. For teams that already produce events in production, this is the natural evolution, not a rewrite.
Checklist before adopting EDA
- Does every domain event carry an
event_idand a versioned schema in the registry? - Are all your consumers idempotent and replay-safe, or do you trust "the broker never duplicates"?
- Do you know whether your case is work (queue) or history (stream) — or are you using a stream as an expensive queue?
- Do you have sized retention and partitioning policies, not the defaults?
- Can you spin up a new consumer and rebuild its state from the log without touching producers?
EDA does not solve distributed consistency: that is what saga patterns are for. But choosing well between queue and stream, making duplicates boring with idempotency, and contracting your events with versioned schemas is what separates an architecture that scales from one that only looks modern.