Why Microservices?
Microservices are not a goal — they are a trade-off you make to solve specific problems. This page lays out the real drivers that justify the architecture and, just as importantly, the costs you sign up for. It ends with the question that matters most: when should you not use microservices? Be skeptical of anyone who answers that with “never.”
The real drivers
Independent deployment
The headline benefit. In a monolith, a one-line fix to the reporting module still triggers a full build, test, and redeploy of the entire app. With microservices, each service has its own pipeline:
payments-service → fix → deploy at 14:02 (orders unaffected)
orders-service → no deploy needed
Smaller deploys mean smaller blast radius, faster rollback, and more frequent releases — the core enabler of true continuous delivery for large systems.
Independent scaling
You scale the bottleneck, not the whole app. A checkout service under Black-Friday load can run 30 replicas while the rarely-touched admin service runs one. Resource spend tracks actual demand.
Team autonomy
Conway’s Law cuts both ways. When ten teams share one codebase, every change is a negotiation. Give each team a service with a clear API contract and they can choose their own release cadence, on-call rotation, and internal design without a standing meeting.
Technology diversity
A team can adopt a new Java version, swap Postgres for MongoDB, or write a CPU-bound service in a different language — all without forcing that choice on everyone. Use this sparingly; every new stack is a new thing to operate.
Fault isolation
A memory leak in the recommendations service should not crash checkout. With process and deployment boundaries, plus a Circuit Breaker, a failure in one service degrades gracefully instead of cascading.
| Driver | Monolith pain it removes |
|---|---|
| Independent deploy | Full-app redeploy for any change |
| Independent scale | Over-provisioning the whole app for one hot feature |
| Team autonomy | Coordination tax of a shared codebase |
| Tech diversity | One-stack-fits-all lock-in |
| Fault isolation | A single bug crashing everything |
The costs (read these twice)
Distributed systems are hard
An in-process method call either returns or throws. A network call can return, throw, time out, partially succeed, succeed-but-you-never-hear-back, or succeed-twice. Every inter-service call needs timeouts, retries, and idempotency. The fallacies of distributed computing become your daily reality.
Data consistency
You lose ACID transactions across services. Keeping orders, inventory, and payments consistent now requires the Saga Pattern, compensating actions, and accepting eventual consistency. Reports that were a simple JOIN become API composition or data replication.
Observability is mandatory, not optional
In a monolith a stack trace tells the whole story. In microservices one user request fans out across five services. Without Distributed Tracing, centralized logging, and metrics, debugging is guesswork.
GET /checkout → gateway → orders → inventory → payments → notifications
one request, five services, five sets of logs
Network and latency
Every hop adds latency and a new failure mode. A request that touched three modules in microseconds may now make three network calls measured in milliseconds, each able to fail independently.
Operational overhead
You now run service discovery, an API gateway, a config server, container orchestration, and CI/CD for N services instead of one. This needs platform engineering investment that a small team may not have.
Testing and local development
End-to-end testing no longer means running one app. You need contract tests between services, integration environments, and a way to run (or convincingly mock) a dozen dependencies on a laptop. A change that crosses a service boundary can’t be verified by a single unit test.
| Cost | Concretely, you now own |
|---|---|
| Distributed systems | Timeouts, retries, idempotency on every call |
| Data consistency | Sagas, compensations, eventual consistency |
| Observability | Tracing, log aggregation, per-service metrics |
| Network | Extra latency, partial failures, serialization |
| Operations | Discovery, gateway, config, orchestration, N pipelines |
| Testing | Contract tests, integration envs, local mocking |
Warning: Microservices without automation (CI/CD, infrastructure-as-code, observability) produce a distributed monolith — all the costs, none of the benefits. If you can’t deploy a service independently in one click, you’re not ready.
When NOT to use microservices
Be opinionated here. You should probably not start with microservices when:
- The domain is unproven. Service boundaries follow bounded contexts; if you don’t yet understand the domain, you’ll draw the boundaries wrong and pay dearly to redraw them across the network.
- The team is small. Five engineers running fifteen services spend more time on plumbing than on product.
- You lack the platform. No mature CI/CD, container platform, or observability stack means microservices will hurt more than help.
- Your scaling needs are uniform and modest. If the whole app scales together fine, the granularity buys you nothing.
A pragmatic default
Start with a modular monolith (see Monolith vs Microservices). Keep clean module boundaries and own data per module. Extract a service only when a module has a concrete, present need — independent scaling, a dedicated team, or a divergent release cadence.
| Situation | Recommendation |
|---|---|
| Startup / new product | Modular monolith |
| Stable product, small team | Stay monolith |
| Several teams, clear contexts | Microservices for high-friction areas |
| One module needs to scale 10× others | Extract that module |
| ”It’s the modern way” | Not a reason — stay put |
Tip: The best microservices architectures are usually grown by extracting from a monolith that revealed its seams under real load — not designed up front on a whiteboard.
If those drivers genuinely apply to you, the next page shows how Spring Boot and Spring Cloud make this architecture practical: Microservices with Spring.