Design & Architecture

Event-Driven Architecture

Advanced

In an event-driven architecture, components communicate by publishing and reacting to events rather than calling each other directly. A service announces that something happened; other services that care subscribe and respond, without the publisher knowing they exist. Done well it gives you loose coupling and resilience; done carelessly it gives you a distributed mess that is impossible to follow.

The shift is from "A calls B and waits" to "A emits an event; whoever cares handles it." The publisher is decoupled from its consumers — you can add a new consumer without touching the producer — and work happens asynchronously, so a slow or down consumer doesn't block the producer. This is the architecture-level expression of the messaging patterns in Asynchronous Messaging & Eventing.

That decoupling has a price: flow becomes implicit and harder to trace, ordering and duplication are facts of life, and "where did this go wrong?" spans many services. Event-driven design is the right tool when components must evolve and scale independently or react to things happening elsewhere — and the wrong one when you really just need a synchronous request/response. It builds on Distributed Systems & Consistency, and the events themselves are often discovered via Event Storming and stored via Event Sourcing.

Design the events and the contracts

Hidden synchronous coupling // "event-driven", but the publisher orchestrates everyone:
Publish(paymentEvent);
await emailService.Send(...); // direct call
await ledgerService.Post(...); // direct call
await screeningService.Check(...);// now coupled to all three

The producer knows and waits on every consumer. This is request/response wearing an event costume — none of the decoupling, all of the indirection.

Publish a fact, react independently await bus.Publish(new PaymentCaptured(paymentId, amount, at));
// email, ledger, and screening each subscribe and handle it
// on their own, idempotently; the publisher knows none of them

The producer states a fact and moves on. Consumers evolve, scale, fail, and retry independently. Adding a fourth reaction touches no existing code.

Make consumers robust

Keep it observable and bounded

Self-review checklist

Why it matters: Event-driven architecture buys independent evolution, elastic scaling, and resilience — services react to facts without being chained to each other. But it trades explicit, traceable flow for implicit choreography, and it makes duplicate, delayed, and out-of-order delivery your problem to handle. Used where that trade-off pays — and backed by idempotency, the outbox pattern, dead-letter handling, and tracing — it keeps a complex system loosely coupled and correct rather than mysteriously broken.