Event-driven Architecture
Method
A design approach where services announce that something happened instead of calling each other directly, and whoever cares picks the announcement up.
Why it exists
As a product grows, so does the list of things that have to happen alongside a single action. Saving a document in Pagedeck should notify the people editing it, rebuild the search index, add a line to the activity log, and feed the team usage counter.
The obvious way to do that is to have the saving code call those four in order. It holds up for the first two. After that, every new feature means opening the save code and adding another line, and six months later the save function knows about seven things that have nothing to do with saving. Change any one of them and you have to retest saving — that is what high coupling looks like.
The worse part is that failure and latency come along for the ride. If notifications slow down, the save button slows down with them. If indexing throws an error, the save fails too. Theo ends up saying that documents can't be saved because the notification server is down, and from the user's side those two things have nothing to do with each other.
Event-driven architecture reverses the direction of the call. The code that saved the document announces one fact — the document was saved — and knows nothing about who acts on it. Notifications, indexing, whoever needs it subscribes and takes it.
How to run it
It takes six steps, from picking which facts to announce through attaching subscribers and handling failure.
- Pick the events — only facts that genuinely happened in the business become events, named in the past tense, like
document.saved. Name onesend_notificationinstead and the publisher is back to knowing its subscribers, which undoes the whole reversal. - Decide the payload — carry enough that a subscriber can work without asking back, such as the document ID, who saved it, and when. Anything large and fast-changing, like the document body, travels as an ID that subscribers fetch when they need it.
- Choose the transport — you need a broker that holds announced events and hands them to subscribers. With a single subscriber, a message queue that lines work up and passes it along one at a time is enough. When several subscribers each need the same event, you want something that copies the event and fans it out.
- Attach the subscribers — the side that subscribes and does its own work. Notifications, indexing, and usage counting each attach separately and each receive the same
document.saved. If one of them stops, the others keep running, and the one that stopped works through the backlog once it recovers. - Handle duplicates and failure — a broker that gets no acknowledgment resends, so the same event arriving twice is normal rather than exceptional. A subscriber therefore needs idempotency, the property that receiving the same event repeatedly produces the same result. Usually that means recording the unique ID on each event and skipping IDs already processed.
- Make the flow visible — attach a correlation ID to each event and carry it through every hop. A direct call leaves the whole path in one error log, but here the path is scattered in pieces, so without observability — the ability to see what a running system is doing from the outside — a report that a notification never arrived gives you no way to find where it broke.
When it fits and when it doesn't
It fits when one thing happening needs several reactions and the list is likely to grow. A payment-approved event can carry a receipt send, a usage update, an accounting entry, and a heads-up to customer success. Going from four subscribers to six doesn't mean opening the payment code.
It also fits when the publisher has no reason to wait for a result. The user only needs to hear that the document was saved, and whether the notification goes out three seconds or thirty seconds later doesn't matter to them.
Three limits come back the other way.
- It doesn't suit anything that needs an immediate answer. Whether a payment was approved has to resolve while the user is watching the screen, and you can't draw that screen by announcing an event and processing it later.
- Being briefly out of sync is the normal state. There are always a few seconds where the save has finished but search hasn't caught up. Decide first whether the product side accepts those seconds, and if it does, what the screen shows in the meantime.
- Tracing is harder. Checking one report about a missing notification means opening the publish log, the broker state, and the subscriber log separately. At forty-five people, applying this everywhere costs more in looking things up than it returns. The usual shape is a modular monolith — one deployable with enforced module boundaries inside — with events used only in the few places that genuinely have several subscribers.
How it sounds at work
"Don't call that from the save path — pull it out into an event."
A proposal to have the new feature subscribe instead of being wedged into existing code.
"The notification could land about 20 seconds late. Is that acceptable?"
Checking whether the product side accepts that delay before moving to events.
"Is that consumer idempotent?"
Asking whether a duplicate event would send the receipt twice.
Pagedeck uses this in exactly two places: document saves and payments. When Mia brings a feature that needs something to happen after a save, Theo adds another subscriber to an existing event and leaves the save code untouched.
What people get wrong
The most common reading is that processing things asynchronously makes the system faster overall. The save button really does come back sooner, so the impression is easy to form. The total amount of work hasn't changed, though. What got faster is when the user receives a response; when the notification actually arrives gets later, not sooner. If events pile up faster than subscribers can drain them, the backlog keeps growing.
Treating event-driven architecture and microservices as the same thing is the other frequent mistake, mostly because splitting services often means stitching them back together with events. They answer different questions. Microservices decides how many deployable pieces the system is cut into; event-driven architecture decides how those pieces talk. Events work fine inside a single-deployable monolith, and plenty of finely split systems do nothing but call each other directly. Conflate the two and a team that isn't ready to split services starts by splitting them anyway.
Easily confused with
| Term | What it is | How it differs |
|---|---|---|
| Message queue | A device that lines work up and hands it off asynchronously | One component that carries events. Event-driven architecture is the design decision about what counts as an event and how pieces connect; a message queue is a tool you use to actually run that design |
Where to go next
- Software architecture
see which set of options this approach sits among
- Monolith
the baseline shape where pieces call each other directly
Last updated 2026-09-16