Event-Driven Architecture
Event-driven architecture (EDA) structures a system around the production, detection, and consumption of events — immutable facts stating that something happened, such as OrderPlaced or PaymentFailed. Instead of a service calling another service's API and waiting for a response, a producer emits an event to a broker and any number of consumers react to it independently, on their own schedule. This inverts the typical request-response dependency graph: producers do not know who is listening, and consumers do not know who produced the event. The result is a system where services can be added, removed, or scaled without touching the code of the services around them, because the contract is the event schema, not a direct call.
Cricket analogy: Like a stadium announcer declaring 'WICKET!' over the PA without knowing who's listening — the scoreboard, replay team, and stats analysts each react independently on their own schedule, and new services like a fan app can start listening without touching the announcer's setup.
Choreography vs. Orchestration
There are two broad ways to coordinate behavior across services in an event-driven system. In choreography, each service listens for events and decides on its own what to do next, publishing further events as a result — there is no central coordinator, so the overall workflow emerges from many local decisions. In orchestration, a central controller (an orchestrator or workflow engine) explicitly tells each participant what to do and in what order, often by calling them directly or dispatching command-like events. Choreography scales organizationally well because teams own their own reactions and add new listeners without asking permission, but it makes the end-to-end flow hard to see and debug. Orchestration keeps the flow explicit and easy to reason about but reintroduces a central point of coordination and potential coupling.
Cricket analogy: Like a fielding side where each fielder reacts on their own to where the ball goes with no captain directing every move (choreography, flexible but hard to predict), versus a captain calling every fielder's position explicitly before each ball (orchestration, predictable but centralized).
Event Notification vs. Event-Carried State Transfer
A thin event notification carries just an identifier and a type, such as 'OrderId 8842 was placed', forcing the consumer to call back to the source service to fetch full details — this keeps events small but reintroduces synchronous coupling at read time. Event-carried state transfer instead embeds the relevant data directly in the event payload, letting consumers build and maintain their own local copy of the data they need without calling back. This trades a larger, more schema-sensitive event for the elimination of runtime dependencies between services, and it is the pattern most commonly used to build read-optimized, denormalized views in downstream services.
Cricket analogy: Like a scoreboard flashing just 'WICKET, Player 7' forcing commentators to call the dressing room for details (thin event, small but requires a callback), versus the broadcast feed embedding the dismissal type, bowler, and replay directly in the graphic (state transfer, self-contained but heavier).
Choreographed order fulfillment (no central coordinator)
[Order Service] --OrderPlaced--> (event bus)
|--> [Inventory Service] --InventoryReserved--> (event bus)
| |--> [Payment Service] --PaymentCaptured--> (event bus)
| |--> [Shipping Service] --ShipmentCreated--> (event bus)
|--> [Analytics Service] (reads OrderPlaced independently, no downstream effect)
Each service reacts only to events it cares about; none of them
call each other's APIs directly, and each can be deployed, scaled,
or replaced without changing the others' code.LinkedIn's Apache Kafka, originally built in-house to move activity and metrics data between systems, has become the de facto backbone of large-scale event-driven architectures. Companies like Netflix and Uber use Kafka topics as durable, replayable event logs so that new consumers — for example a fraud-detection service added a year after launch — can subscribe and reconstruct history from retained events instead of requiring the producer to change.
A common mistake is treating an event bus as just a faster message queue and shipping the same tightly-coupled request-response logic through it. If Service B cannot make progress without a synchronous reply from Service A, publishing an event instead of calling an API does not remove the coupling — it just hides it behind asynchronous plumbing and makes failures harder to trace.
Schema Evolution and Consumer Independence
Because producers and consumers are decoupled in time and deployment, event schemas must evolve compatibly: new optional fields can usually be added freely, but renaming or removing a field a consumer depends on will silently break that consumer at runtime rather than at compile time. Teams typically manage this with a schema registry that enforces backward- or forward-compatibility rules before a new producer version is allowed to publish, and with versioned event types (OrderPlacedV2) when a breaking change is unavoidable.
Cricket analogy: Like adding a new 'strike rate' field to the scorecard format that old apps simply ignore (safe, additive), versus renaming the 'runs' field to 'score' which silently breaks every old scoreboard app reading it — requiring a new 'ScorecardV2' format and a board-approved format registry before rollout.
- Event-driven architecture decouples producers and consumers through an intermediary broker, so neither needs to know about the other.
- Choreography distributes decision-making across services; orchestration centralizes it in a coordinator — each trades debuggability for coupling differently.
- Event-carried state transfer embeds payload data in the event to avoid callback coupling, at the cost of larger, schema-sensitive events.
- Kafka-style durable, replayable logs let new consumers be added long after events were originally produced.
- Schema evolution must be managed deliberately (e.g. with a schema registry) because consumers cannot be forced to upgrade in lockstep with producers.
- EDA improves scalability and team autonomy but adds complexity in tracing end-to-end flows and debugging distributed failures.
Practice what you learned
1. What is the key difference between choreography and orchestration in event-driven systems?
2. What problem does event-carried state transfer solve compared to a thin event notification?
3. Why are durable, replayable event logs like Kafka valuable in event-driven architecture?
4. Which practice is most important for safely evolving event schemas over time?
5. What is a common anti-pattern when adopting event-driven architecture?
Was this page helpful?
You May Also Like
Pub/Sub Architecture
Publish-subscribe architecture decouples message producers from an unknown, dynamic set of consumers by routing messages through named topics rather than direct point-to-point links.
Message Queues Explained
Message queues decouple producers from consumers by buffering work as discrete messages, enabling asynchronous processing, load leveling, and resilience to downstream slowdowns.
Idempotency in Distributed Systems
The property that performing the same operation multiple times produces the same result as performing it once, essential for safely retrying requests over unreliable networks.
Circuit Breakers and Bulkheads
Resilience patterns that stop cascading failures — circuit breakers halt calls to a failing dependency, while bulkheads isolate resource pools so one failure cannot exhaust everything.
Related Reading
Related Study Notes in Software Engineering
Browse all study notesMicroservices Study Notes
Software Architecture · 30 topics
Software EngineeringTesting & TDD Study Notes
Software Testing · 30 topics
Software EngineeringDesign Patterns Study Notes
Software Design · 30 topics
Software EngineeringSoftware Engineering Study Notes
Python · 40 topics
Software EngineeringGit & Version Control Study Notes
Bash · 40 topics