Event-driven architecture decouples producers from consumers and enables asynchronous work, high throughput, and clean audit trails.
Event-driven architecture (EDA) is the pattern in which system components communicate primarily by producing and consuming events on a message bus rather than by direct synchronous calls. Well-applied it unlocks throughput, resilience, and clean team boundaries. Naively applied it produces a distributed monolith with worse debuggability than the synchronous version it replaced. The interesting questions are which parts of the system belong on the bus and which do not.
Queue (SQS, RabbitMQ) — one producer, one consumer per message; delete after processing. Best for task offloading (send email, resize image). Stream (Kafka, Kinesis, Pulsar, Redpanda) — durable ordered log; multiple independent consumers can replay from any offset. Best for analytics fanout, event sourcing, cross-team integration. Pub/sub (SNS, Google Pub/Sub) — fire-and-forget broadcast. Best for notifications and loose coupling between services. Choose by ordering, replay, and consumer count requirements — not by fashion. Mixing them per use case is normal and correct.
The classic bug: you commit a row to Postgres and publish an event to Kafka, one succeeds and one fails, state and events drift. The outbox pattern fixes this: in the same DB transaction that changes state, INSERT a row into an outbox table. A separate publisher process reads from outbox and publishes to the bus, marking rows sent. If the publisher crashes, unsent rows are retried on restart. Guarantees at-least-once delivery correlated with state changes, without distributed transactions. Every EDA system that cannot afford lost events should implement outbox (or use a change-data-capture tool like Debezium that reads the DB log and publishes automatically).
Assume every event will be delivered more than once (queue redelivery, publisher retry after crash, consumer replay after bug fix). Design consumers to be idempotent: include a stable event_id in every event; consumer stores processed event_ids in an idempotency store; skip re-processing. For side effects (charge a card, send an email), rely on downstream idempotency keys (Stripe supports them; email providers have deduplication windows). Never assume exactly-once delivery — the true 'exactly-once' guarantees you'll find in vendor marketing depend on strict conditions that break under real failure modes.
Events published today will be consumed by services you have not written yet. Breaking an event schema breaks unknown downstream consumers you cannot easily coordinate with. Rules: (1) Schema registry (Confluent Schema Registry, AWS Glue, Buf Schema Registry) — every event conforms to a versioned schema, compatibility checks in CI. (2) Additive changes only — add optional fields; never rename, never change types. (3) Deprecation cycle — mark fields deprecated, wait 90 days minimum, remove in a new schema version. (4) Publish schema changes in a changelog. Investment here pays off compounded — an EDA without schema discipline collapses under its own weight in 18 months.
A synchronous request has a stack trace. An event-driven flow has an event that produced another event that produced a state change three services later. Investment required: (1) trace_id propagated through every event and correlated in traces (OpenTelemetry, Datadog APM, Honeycomb). (2) Event lineage tooling — a UI that shows, for one event_id, every consumer that processed it and every event it produced. (3) A 'reprocess from this point' operation for recovering after a bug fix. (4) Dead-letter queues with alerts and a UI to inspect and retry. Do not adopt EDA without budgeting for this tooling — the cost of debugging without it is measured in outages.
Investor directory · Fundraising library · Articles A–Z · Company funding database