22 Jun 2026·6 min read·Architecture

Event-driven by default: designing services that don't call each other

Wiring microservices together with direct calls feels productive - right until you've built a distributed monolith. Here's why I make events the default, and what that choice actually costs.

Split a system into services and the first question is always the same: how do they talk? The obvious answer - service A calls service B over HTTP - is also the one that quietly undoes most of the reason you split things up in the first place.

Every direct call is a hard dependency. A needs B to be up, fast, and on the version it expects. Chain a few of those together and you've rebuilt the monolith you were trying to escape - except now the function calls cross the network, where they can time out, retry, and cascade. It looks like microservices on the architecture diagram. It fails like a monolith in production.

A direct call says "I need you, right now." An event says "this happened - do what you want with it." Only one of those scales to a team you'll never meet.

Publish facts. Don't orchestrate.

The default I reach for now: when something meaningful happens in a service, it emits a domain event - a statement of fact about the past - and moves on. It doesn't know or care who's listening. Other services subscribe to the events they care about and react on their own schedule.

Concretely, on the estate I work on, a write looks like this:

# write side
command  → update DynamoDB          # source of truth
         → Streams emit change
         → EventBridge domain event   # "ProductChanged"

# anyone interested reacts, independently
search-indexer   → update OpenSearch   # read model
partner-push     → orchestrator → worker fan-out
audit            → append-only log

The service that changed the product never called the search indexer, the partner-push pipeline, or the audit log. It published one fact. Three unrelated capabilities reacted. Tomorrow a fourth subscriber can appear without the producer changing a single line.

Fan-out is the superpower

This is where event-driven earns its keep. A config change in one service fans out to every product that depends on it, as events - not as a fragile chain of synchronous calls the producer has to know about. For heavy work like pushing updates to external partners, an orchestrator receives the event and emits per-item work events that a pool of workers handle in parallel. Load levels itself. Failures retry in isolation instead of taking down the request that triggered them.

The honest trade-offs

Events are not free, and anyone who sells them as a pure win hasn't run them. What you're really buying:

What I'd tell you to steal

Event-driven architecture isn't about message brokers or any particular cloud service. It's a stance: services should depend on facts, not on each other. Get that right and the estate stays loosely coupled as it grows. Get it wrong and you've just made your monolith harder to debug.

Designing a service estate?

I architect distributed systems and AI platforms that stay loosely coupled as they scale. Happy to trade notes.

Get in touch →