Monolith vs Microservices
A monolith is a single deployable unit that contains every feature; microservices decompose an application into independently deployable services that collaborate over the network. The right choice is a trade-off between operational simplicity and organizational scale, not a matter of fashion.
What is this?
A monolith ships all features — UI, business logic, data access — as one process and one artifact. Microservices split the system by business capability, giving each service its own codebase, data store and deploy pipeline that talk over HTTP, gRPC or events. Both are architectural strategies for managing change, coupling and scaling, and the decision is reversible.
Why do you need it?
- Monoliths have the lowest operational overhead: one build, one deploy, one place to trace a request.
- Microservices let each service deploy, scale and fail independently, so a hot path scales without replicating the whole application.
- Independent services map cleanly to team ownership and bounded contexts, reducing cross-team merge conflict and coordination.
- A well-drawn service boundary isolates blast radius: a crash in fraud detection does not take down checkout.
- The strangler-fig pattern lets you modernize a legacy system incrementally instead of doing a big-bang rewrite.
Mental model
A single restaurant kitchen
A fleet of specialty food trucks
One kitchen serves every table under one roof — simple, but every new menu item touches the same stove. Trucks each own one menu and can be added, scaled or retired independently, but coordinating orders between trucks introduces delivery logistics the kitchen never needed.
How it works
- A monolith is one codebase and one artifact (JAR, bundle, container image) where every request is handled in a single process.
- Microservices are split by business capability; each service owns its data and exposes an API rather than sharing a database.
- Services communicate over the network, so the request path spans multiple hops and latency, retries and timeouts become first-class concerns.
- Decomposition is driven by team boundaries, ownership and change frequency — split what changes together and is owned together, not by file count.
- An API gateway or service mesh fronts the services to handle routing, auth, rate limiting and request tracing.
Request path in a monolith versus microservices: an in-process call becomes a distributed fan-out.
Key commands
node server.jsStart the monolith locally on port 3000.
curl -s localhost:3000/api/orders/1Call an endpoint inside the single process to verify the monolith.
{"id":1,"customer":"ada","total":49.99}docker build -t store-api .Package the monolith into a single container image.
docker-compose up -dBring up the full stack (api, db, cache) in one command.
npm testRun the test suite once for the whole monolith.
git tag v1.0.0Tag a deployable version of the monolith for release tracking.
Examples
A two-route monolith before decomposition
// monolith/server.js - one process handles every featureconst express = require("express")const app = express() app.get("/api/users/:id", (req, res) => { const user = db.users.findById(req.params.id) // shared database res.json(user)}) app.get("/api/orders/:id", (req, res) => { const order = db.orders.findById(req.params.id) // same shared database res.json(order)}) app.listen(3000)Users and orders live in the same process and share one database and one deployment unit. Extracting the orders route into its own service means moving the data access into the service, exposing it over HTTP, and having the gateway forward /api/orders there — that new network hop is the cost you take on.
In production
- A startup ships a well-modularized monolith first to reach product-market fit with the smallest operational surface.
- A payments platform splits fraud detection, ledger and notifications so each scales and fails independently.
- An e-commerce platform splits catalog (read-heavy, cacheable) from checkout (write-heavy, transactional) to tune each path.
- Teams use the strangler-fig migration, standing up new capabilities as services and cutting over old monolith routes incrementally.
Best practices
- Start with a modular monolith; extract a service only when a stable seam, team boundary and data boundary all exist.
- Enforce database-per-service: never let a service reach into another service's tables.
- Define explicit, versioned contracts (OpenAPI or protobuf) so producers and consumers evolve independently.
- Keep every service independently deployable and feature-flagged so you can roll back without a coordinated release.
- Measure before splitting — latency, deploy cadence and team friction — so the move is driven by data, not dogma.
Common mistakes
- Building a distributed monolith: services that must deploy together or share a database.
- Splitting by technical layer (front, back, db) instead of by business capability, which maximizes chatty coupling.
- Ignoring data coupling and designing synchronous cross-service transactions that need distributed locking.
- Over-engineering: spinning up dozens of services for a five-person team that ships one CRUD app.
Common issues
ECONNREFUSED calling the orders serviceThe downstream service is down or registered under the wrong address.
Check the service's health endpoint, confirm service discovery / DNS, then look at its logs on startup.
End-to-end p95 jumped from 50ms to 500msChatty request graphs where one user action triggers many sequential calls.
Batch or fan-in calls, add read-model caching, and move non-critical steps to events.
Order and payment data are inconsistentTwo services writing to the same logical transaction without coordination.
Introduce a saga with compensating actions or an outbox pattern so each step is eventually consistent.
Debugging tips
- Start from the tracing system (Jaeger, Zipkin, Datadog) to see the full request path across services.
- Check every service's health endpoint before assuming the problem is in the application logic.
- Thread a correlation ID through headers so log lines across hops can be joined into one trace.
- Verify deployed versions — a stale or mismatched service version is the classic source of 'works on my machine'.
Interview checkpoint
Hands-on lab
- Stand up a two-route monolith API with a shared data store
- Identify a bounded capability worth extracting
- Extract the orders capability into its own HTTP service
- Route the gateway to the extracted service and verify the split
From Monolith to Service
45 min · intermediate
Quick quiz
What is the primary cost you accept when you move from a monolith to microservices?
In a well-designed microservice system, services commonly share a single database.
A four-person team must ship a CRUD application in six weeks with no SLA beyond availability. Which starting architecture is most defensible?
Cheat sheet
Same deploy cadence?Keep it together; do not splitDifferent scale profileCandidate for its own serviceTeam boundary existsService per team ownershipData accessed across servicesIntroduce an API or event, never shared tablesNew capability in legacy appStrangler-fig patternAPI gatewayRoute, auth, rate-limit external callsService meshmTLS, retries, canary traffic, tracingFeature flagsSafe cutover and instant rollbackCorrelation IDsJoin logs across hops