Microservices vs Monolith: When to Use Each

A monolith packages an entire application as one deployable unit, simple to build, test, and deploy, but it scales as a whole and couples teams. Microservices split the app into independent, separately deployable services owned by small teams, enabling independent scaling and fault isolation at the cost of distributed-system complexity, network calls, and harder data consistency.

The Two Architectures

A monolith is a single codebase and deployable artifact containing all functionality, UI, business logic, and data access, typically talking to one database. It's the simplest way to start: one repo, one deploy, in-process function calls, easy local testing, and straightforward transactions.

Microservices decompose the application into many small services, each owning a bounded context (e.g., payments, inventory, notifications), its own database, and its own deployment pipeline. Services communicate over the network via REST, gRPC, or message queues. Amazon, Netflix, and Uber famously moved from monoliths to microservices to scale engineering organizations of thousands.

Tradeoffs

The core tension: monoliths optimize for simplicity and speed early; microservices optimize for team autonomy and independent scaling at larger size, paying an operational tax to get there.

DimensionMonolithMicroservices
DeploymentOne unit, simpleIndependent per service
ScalingWhole app togetherPer-service, granular
Fault isolationOne bug can crash allFailures contained
DataSingle DB, easy ACIDDB per service, eventual
Ops complexityLowHigh (orchestration, observability)
Team autonomyCoupledIndependent ownership
LatencyIn-process callsNetwork hops

When to Use Each

Start with a monolith for new products and small teams: it's faster to build, easier to refactor while domain boundaries are still uncertain, and avoids premature operational overhead. Many successful companies ran monoliths to large scale (Shopify, Stack Overflow, GitHub all run large monoliths). The 'modular monolith', clear internal module boundaries in one deployable, captures much of the benefit without the distribution tax.

Move to microservices when concrete pain appears: teams blocked on each other's deploys, parts of the system needing very different scaling (a CPU-heavy ML service vs a CRUD API), or fault isolation requirements. The common, pragmatic path is to extract services from a monolith incrementally along proven domain boundaries, not to start with dozens of services on day one (Martin Fowler's 'MonolithFirst').

Data and Communication Patterns

Each microservice should own its data (database-per-service) to stay decoupled; sharing a database recreates coupling. Cross-service consistency uses sagas (a sequence of local transactions with compensating actions) and the outbox pattern to reliably publish events. Synchronous calls (REST/gRPC) are simple but create coupling and cascading failures; asynchronous messaging (Kafka, RabbitMQ) decouples services and improves resilience. Resilience patterns, timeouts, retries with backoff, circuit breakers (Hystrix/Resilience4j), and bulkheads, become mandatory.

ResuMax tailors your resume to each role, scores it like a recruiter, and preps you for interviews.

Practice with the interview coach

Frequently asked questions

Should I start with microservices or a monolith?

Almost always start with a monolith (or a modular monolith). It's faster to build and easier to change while domain boundaries are still uncertain. Extract microservices later when you hit concrete pain: team coupling, divergent scaling needs, or fault-isolation requirements.

What is a distributed monolith?

A distributed monolith is microservices that are so tightly coupled they must be deployed together, combining the operational complexity of distribution with the rigidity of a monolith. It usually results from poorly drawn service boundaries and shared databases.

How do microservices handle transactions across services?

Since each service owns its database, you can't use a single ACID transaction. Instead, use the saga pattern, a sequence of local transactions with compensating actions to undo on failure, plus the outbox pattern and event messaging for reliable, eventually consistent updates.

Why do microservices need circuit breakers?

Because services call each other over the network, a slow or failing dependency can cascade and exhaust resources across the system. A circuit breaker (e.g., Resilience4j) detects failures and short-circuits calls to the failing service, failing fast and allowing recovery.

Related