Architecture

Event-driven architecture: decoupling without losing track

Events decouple producers from consumers and make the overall flow harder to see. Both effects are real and need managing.

Key takeaways

  • Events decouple producers from consumers and hide the overall flow.
  • Duplicate delivery is normal; idempotency must be designed in.
  • Name events as facts and evolve schemas additively.
  • Dead letter queues, consumer lag alerts and tracing are essential.

What events give you

A producer emits a fact without knowing who consumes it. New consumers are added without changing the producer, work is absorbed asynchronously during spikes, and a slow consumer does not block the request path.

For workflows with several independent reactions to the same occurrence, this is a genuinely better model than direct calls.

What you give up

The complete flow is no longer visible in one place. Debugging requires tracing across components, ordering guarantees vary by transport, and duplicate delivery is normal rather than exceptional.

Consumers must therefore be idempotent, and that has to be designed rather than assumed.

Design the events themselves

  • Name events as facts that occurred, not as commands.
  • Include enough data that common consumers need no follow-up call.
  • Version the schema and evolve additively.
  • Include a unique identifier and a timestamp for deduplication and ordering.

Operational essentials

Dead letter queues for messages that cannot be processed, with someone responsible for reviewing them. Monitoring on consumer lag, because a silently falling-behind consumer is a common failure.

Distributed tracing carrying a correlation identifier through the whole chain is what makes an event-driven system debuggable. Without it, investigating a problem becomes archaeology across several logs.

Testing an event-driven system

Testing is harder when the flow spans components, and teams often compensate with heavy end-to-end tests that are slow and brittle.

Test each consumer in isolation against representative events, use contract tests to verify that producers emit what consumers expect, and reserve a small number of end-to-end tests for the most important flows.

Make events replayable in a test environment. Being able to feed a recorded sequence through a consumer is the fastest way to reproduce a production problem.


Frequently asked questions

Managed queues suit straightforward asynchronous work. Log-based systems like Kafka suit high throughput, replay and multiple independent consumers of the same stream. Start with the simpler option.

Ordering guarantees vary. Where it matters, partition by a key so related events are ordered relative to each other, and design consumers to tolerate what the transport does not guarantee.

No. Synchronous calls are simpler and appropriate when the caller needs an immediate answer. Use events where reactions are genuinely independent.

Working on this?

A conversation with our team, no charge and no obligation.

Book a conversation