Monolith First
the default nobody defends enoughThe right default is a well-structured monolith: one deployable unit with clean internal modules. It's not the primitive option — it's the one that keeps the things microservices throw away. Calls between modules are local function calls (fast, no partial failure). A change spanning two areas is one atomic database transaction. There's one thing to deploy, one place to debug, one log to read.
Most systems don't die from lack of scale. They die from complexity. A monolith you can reason about beats a distributed system you can't.
What Microservices Actually Buy
and why the driver is usually peopleMicroservices deliver real benefits — just fewer, and later, than the hype suggests.
| Benefit | What it means |
|---|---|
| Independent deploys | Ship one service without redeploying the whole system |
| Independent scaling | Scale the hot service alone instead of the entire app |
| Team autonomy | A team owns a service end to end, chooses its own stack and pace |
| Fault isolation | One service crashing needn't take the others down (with the right patterns) |
What They Cost
every local call becomes a network callSplitting turns in-process certainties into distributed problems. The costs are concrete, and they show up on day one, not at scale.
MONOLITH MICROSERVICES order.charge(card) POST http://payment/charge ├─ function call: ~nanoseconds ├─ network call: ~milliseconds ├─ either returns or throws ├─ may time out, retry, half-fail └─ one DB transaction wraps it all └─ no shared transaction (saga needed)
| Cost | Bites you as |
|---|---|
| Network everywhere | Latency, timeouts, partial failure on every call (Resilience) |
| Distributed transactions | No cross-service ACID — sagas and compensation (Transactions) |
| Operational surface | Many deploys, dashboards, versioned contracts, tracing (Telemetry) |
| Local dev pain | Running twelve services to test one feature |
Drawing Boundaries
around capabilities, not entitiesSplit around business capabilities — checkout, search, notifications — not around database entities. A capability owns its data and exposes behavior; an entity-shaped service ("the User service") ends up called by everyone for everything and becomes a shared bottleneck.
The load-bearing rule is database-per-service. Each service owns its data and no other service touches that database directly — access goes through the API. Share a database and you've recoupled everything: a schema change in one service breaks another, and you're back to a monolith with network calls in the middle. Getting data across boundaries then happens three ways: synchronous API calls, events for async propagation (Messaging), or maintaining a local read replica of another service's data via its event stream.
RIGHT: each service owns its store WRONG: shared database
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ orders │ │ payment │ │ search │ │ orders │ │ payment │
│ ↓ own │ │ ↓ own │ │ ↓ own │ └────┬────┘ └────┬────┘
│ DB │ │ DB │ │ DB │ └────┬──────┘
└─────────┘ └─────────┘ └─────────┘ ┌────┴────┐
cross-service data via API / events │ shared DB│ ← recouples both:
└─────────┘ a schema change
breaks everyone
Decomposition Path
strangler fig, never a big-bang rewriteYou don't rewrite a monolith into microservices in one heroic push — that's how projects die. The strangler fig pattern extracts incrementally: route one capability at a time out to a new service, leave the rest in the monolith, and repeat. A routing layer sends the extracted paths to the new service and everything else to the old code, so the system keeps running throughout.
In the Interview
don't default to microservicesReaching for microservices unprompted reads as pattern-matching, not judgment. The senior answer names the tradeoff and defers the split until a requirement forces it.
Quick Reference
monolith vs microservices| Monolith | Microservices | |
|---|---|---|
| Calls | Local, fast, reliable | Network — latency, partial failure |
| Transactions | Real ACID across the app | Sagas, eventual consistency |
| Deploy | One unit | Independent per service |
| Scaling | Whole app together | Per service |
| Teams | Coordinate on one codebase | Autonomous per service |
| Ops burden | Low | High — many moving parts |
| Signals you're ready to split |
|---|
| Teams block each other on one codebase / deploy pipeline |
| One component needs radically different scaling than the rest |
| Parts have conflicting reliability or tech requirements |
| The monolith's build/test/deploy cycle has become the bottleneck |