Microservices vs Monolith: Which Architecture Is Right for You?
The Myth: Microservices Are Always the Modern Choice
In 2023, Amazon Prime Video published a case study explaining how they migrated their video quality monitoring system away from microservices back into a monolith — reducing infrastructure costs by 90%. That same year, a 2025 Gartner survey found that 60% of engineering teams regret adopting microservices for small-to-medium applications. The architecture debate is not about which is "modern" — it is about which is right for your specific constraints.
Key Takeaways
- ▸Microservices infrastructure costs 3.75x–6x more than an equivalent monolith, per 2025 cloud migration cost analyses.
- ▸42% of organizations that adopted microservices are now consolidating services back into larger deployable units.
- ▸The modular monolith is the pragmatic middle ground — monolith deployment simplicity with microservices-ready code organization.
- ▸Microservices are justified when independent scaling needs, team autonomy, or regulatory data isolation requirements are present simultaneously.
- ▸Start with a modular monolith. Extract services only when you have concrete evidence that a specific service needs independent deployment or scaling.
What Is a Monolith?
A monolithic application is deployed as a single unit. All modules — user management, payments, notifications, reporting — run in the same process and share the same database. Code calls are in-process function calls, not network requests.
# Monolith: Everything in one deployable unit
my-app/
├── src/
│ ├── users/ # User management module
│ ├── payments/ # Payment processing module
│ ├── notifications/ # Email/SMS module
│ ├── orders/ # Order management module
│ └── reports/ # Analytics module
├── database/ # Single shared database schema
└── Dockerfile # One container to deploy
# Deploying a change to payments:
git push origin main # → CI builds the whole app → 1 deploymentThe key advantage is simplicity. A cross-cutting operation — "place an order, charge the customer, send a confirmation, update inventory" — is a single database transaction. No distributed coordination, no network failures between services, no partial-failure scenarios.
According to the Stack Overflow Developer Survey 2025, roughly 54% of backend developers still work primarily on monolithic architectures — significantly more than the conference circuit would suggest.
What Are Microservices?
Microservices architecture decomposes the application into independently deployable services. Each service owns its domain, its database, and its deployment pipeline. Services communicate over the network — typically HTTP/REST, gRPC, or async message queues.
# Microservices: Separate deployable units
user-service/ → Port 3001, PostgreSQL db_users
payment-service/ → Port 3002, PostgreSQL db_payments
notification-svc/ → Port 3003, no database (stateless)
order-service/ → Port 3004, PostgreSQL db_orders
report-service/ → Port 3005, ClickHouse analytics_db
# The same "place order" operation now requires:
# 1. POST /orders (order-service)
# 2. POST /payments/charge (payment-service) ← network call
# 3. PUT /inventory/reserve (inventory-service) ← network call
# 4. POST /notifications/send (notification-svc) ← network call
# Deploying a change to payments:
git push origin main # → CI builds ONLY payment-service → 1 deployment
# Other services keep running — no downtime for unrelated changesThe "place an order" transaction that was a single database COMMIT in the monolith now spans 4 network calls. If any step fails partway through, you need a distributed rollback strategy — saga pattern, two-phase commit, or compensating transactions. This is inherently more complex.
The Real Cost of Microservices
The operational overhead of microservices is frequently underestimated. A 2025 cloud migration analysis by Pawel Piwosz documented real migration costs and found microservices infrastructure running 3.75x to 6x more expensive than equivalent monolith deployments for the same functional requirements.
| Infrastructure Component | Monolith | Microservices |
|---|---|---|
| Container clusters | 1 ECS/K8s cluster | 1 cluster + per-service config |
| Databases | 1 PostgreSQL RDS | N databases (one per service) |
| Observability | Single app logs/metrics | Distributed tracing (Jaeger/Tempo), correlated logs across N services |
| Service discovery | Not needed | Consul / K8s DNS / service mesh |
| API gateway | Optional | Required (route, auth, rate-limit) |
| CI/CD pipelines | 1 pipeline | N pipelines + version coordination |
| Engineering overhead | ~1 DevOps engineer | Dedicated platform team (2–5 engineers) |
The Amazon Prime Video case study is the clearest data point. Their video quality monitoring system, built as serverless microservices using AWS Step Functions and Lambda, was hitting scale limits and generating enormous costs from data transfer between services. Consolidating into a single process eliminated the per-transition charges and reduced infrastructure costs by 90% while simultaneously increasing their ability to scale.
When Microservices Are Justified
Microservices are not wrong — they are overkill for most use cases. They are genuinely the right choice when multiple of these conditions apply simultaneously:
1. Independent Scaling Requirements
If your payment service needs 10 instances during Black Friday while your admin panel needs 1, a monolith forces you to scale everything. With microservices, you scale only the services under load. This is valuable at Netflix scale — their recommendation engine needs radically different compute profiles than their billing service.
2. Polyglot Technology Requirements
A machine learning inference service is best implemented in Python with PyTorch. A high-throughput event processing service is best implemented in Go or Rust. A microservices architecture allows each service to use the optimal language and runtime rather than forcing everything into a single tech stack.
3. Team Autonomy at Scale
Conway's Law states that system architecture mirrors communication structure. For organizations with 100+ engineers split into autonomous squads, microservices allow teams to deploy without coordinating release schedules. Amazon's famous "two-pizza team" model — small teams owning services end-to-end — is the human system that makes microservices viable.
4. Regulatory Data Isolation
PCI DSS compliance for payment card data, HIPAA for healthcare records, or GDPR data residency requirements can mandate that specific data categories be isolated in separate systems. Microservices provide clear boundaries for compliance auditors.
// Decision checklist — microservices are justified when:
const shouldUseMicroservices = (
teamSize > 50 // AND
&& hasDistinctScalingRequirements // AND
&& canAffordPlatformTeam // AND
&& (
needsPolyglotTechnology // OR
|| hasRegulatoryIsolationRequirement // OR
|| teamsDeployIndependently
)
)
// Reality check for most projects:
// team of 8, single product, shared infra → shouldUseMicroservices = falseThe Modular Monolith: The Best of Both
The 2025 trend is not "go back to spaghetti monoliths." It is the modular monolith — a single deployable unit with strict internal module boundaries. You get monolith simplicity during development and the code structure that makes future service extraction practical when genuinely needed.
// Modular monolith: strict boundaries enforced in code
// Each module has a public API; direct imports across modules are forbidden
// ✅ payments/index.ts — public API of the payments module
export { createCharge, refundCharge, getPaymentStatus }
// (internal implementation details are NOT exported)
// ✅ orders/OrderService.ts — uses payments via public API only
import { createCharge, getPaymentStatus } from '../payments'
class OrderService {
async placeOrder(cart: Cart, paymentMethod: PaymentMethod) {
// Everything runs in the same process — no network overhead
const charge = await createCharge({
amount: cart.total,
currency: 'usd',
paymentMethod,
})
if (charge.status !== 'succeeded') {
throw new PaymentFailedError(charge.failureCode)
}
// Single database transaction across both modules
await db.transaction(async (trx) => {
const order = await trx('orders').insert({ ... })
await trx('inventory').decrement({ ... })
return order
})
// Notifications are still in-process
await notificationModule.sendOrderConfirmation(order)
return order
}
}
// ❌ FORBIDDEN: bypassing module boundaries
// import { ChargeRepository } from '../payments/repositories/ChargeRepository'
// ↑ This would couple modules at the implementation level
// Lint rule to enforce boundaries (using eslint-plugin-boundaries):
// "boundaries/element-types": ["error", {
// "default": "disallow",
// "rules": [{"from": "orders", "allow": ["payments-api", "inventory-api"]}]
// }]According to ByteIota's 2026 architecture survey, 42% of organizations that adopted microservices are now consolidating services into modular monoliths — not because microservices failed architecturally, but because the operational complexity exceeded the value for their team size and scale.
Head-to-Head Comparison
| Dimension | Monolith | Microservices |
|---|---|---|
| Local development | ✅ One process, one DB | ⚠️ Docker Compose with 10+ services |
| Debugging | ✅ Single stack trace | ⚠️ Distributed tracing required (Jaeger/Tempo) |
| Cross-cutting transactions | ✅ ACID database transactions | ❌ Saga pattern / eventual consistency |
| Independent deployments | ❌ Full redeploy for any change | ✅ Deploy one service without touching others |
| Granular scaling | ❌ Scale the whole app | ✅ Scale individual services |
| Infrastructure cost | ✅ Lower (1–2 servers/containers) | ❌ 3.75–6x higher (per 2025 cost analysis) |
| Technology diversity | ❌ Shared runtime | ✅ Each service can use optimal language |
| Team size fit | ✅ 1–50 engineers | ✅ 50+ engineers (with platform team) |
Implementing Microservices Correctly
If your situation genuinely calls for microservices, the implementation patterns matter as much as the decision. The most common failure mode is creating microservices that are tightly coupled — requiring coordinated deployments and sharing databases — which gives you all the downsides of both approaches with the benefits of neither.
Communication Patterns
// Pattern 1: Synchronous REST/gRPC
// Use when: caller needs an immediate response
// Risk: cascading failures (if payment-service is down, order placement fails)
// order-service calling payment-service directly
const charge = await paymentClient.createCharge({
amount: order.total,
currency: 'usd',
customerId: order.customerId,
})
// Pattern 2: Asynchronous messaging (preferred for resilience)
// Use when: caller doesn't need an immediate response
// Benefit: order-service succeeds even if payment-service is temporarily down
// order-service publishes an event
await messageQueue.publish('order.placed', {
orderId: order.id,
amount: order.total,
customerId: order.customerId,
})
// payment-service consumes the event independently
messageQueue.subscribe('order.placed', async (event) => {
const charge = await processPayment(event)
await messageQueue.publish('payment.completed', {
orderId: event.orderId,
chargeId: charge.id,
status: charge.status,
})
})
// Pattern 3: Saga (distributed transaction)
// Use when: you need multi-service consistency without two-phase commit
class PlaceOrderSaga {
async execute(order: Order) {
try {
const charge = await paymentService.createCharge(order) // Step 1
const reservation = await inventoryService.reserve(order) // Step 2
await notificationService.sendConfirmation(order) // Step 3
return { success: true }
} catch (err) {
// Compensating transactions (rollback)
if (charge) await paymentService.refund(charge.id)
if (reservation) await inventoryService.release(reservation.id)
throw err
}
}
}The API Gateway Pattern
An API gateway is essential infrastructure for microservices: it provides a single entry point for clients, handles authentication, rate limiting, and request routing. Without it, clients must know the address of every service and handle cross-cutting concerns themselves.
# API Gateway routing configuration (Kong / AWS API Gateway style)
routes:
- path: /users/*
upstream: http://user-service:3001
plugins:
- name: jwt-auth
- name: rate-limiting
config: { minute: 100 }
- path: /payments/*
upstream: http://payment-service:3002
plugins:
- name: jwt-auth
- name: rate-limiting
config: { minute: 30 } # Stricter limit for payments
- name: ip-restriction
config:
allow: ["10.0.0.0/8"] # Internal network only
- path: /public/*
upstream: http://public-api:3005
# No auth required
plugins:
- name: rate-limiting
config: { minute: 1000 }The Migration Path: Monolith → Microservices
If you are starting greenfield, start with a modular monolith. If you have an existing monolith and specific services are causing scaling or team bottlenecks, extract those services using the Strangler Fig pattern — route traffic for specific endpoints to a new service while the monolith continues handling everything else.
# Strangler Fig pattern: gradual extraction
# Phase 1: Feature flag routes specific traffic to new service
if (featureFlag.isEnabled('payments-service-v2', userId)) {
// Route to new standalone payment service
return await newPaymentService.post('/charge', body)
} else {
// Legacy monolith path
return monolith.payments.createCharge(body)
}
# Phase 2: New service handles 10% → 50% → 100% of traffic
# Phase 3: Remove old monolith payments code
# Phase 4: Repeat for next service (inventory, notifications, etc.)
# What NOT to do: big-bang rewrite
# 80% of big-bang microservices rewrites fail or significantly delay
# because the domain complexity was underestimated.
# Per the 2024 O'Reilly "Software Architecture" survey,
# incremental migration takes 3x longer but succeeds 4x more often.Who Uses What: Real-World Architecture Choices
The companies cited as microservices success stories were operating at massive scale before they decomposed their monoliths. Netflix decomposed a DVD-by-mail monolith into microservices in 2008–2009 when they had 10 million subscribers and were scaling to streaming. Amazon Web Services itself runs on microservices, but Amazon started as a monolith. Shopify — 10% of US e-commerce — still runs largely on a Rails monolith with specific high-load services extracted selectively.
| Company | Architecture | Key Reason |
|---|---|---|
| Netflix | Microservices (700+ services) | Independent scaling of recommendation, streaming, billing at 270M subscribers |
| Shopify | Modular monolith (core) + selective services | Rails monolith with modularization; only specific high-load areas extracted |
| Amazon Prime Video | Reverted to monolith for video QA | 90% cost reduction by eliminating cross-service data transfer costs |
| Stack Overflow | Monolith | Serves 60M users/month from ~9 web servers; complexity would add no value |
| Basecamp/Hey | Monolith | David Heinemeier Hansson publicly advocates monoliths; team of 60 serves millions |
Frequently Asked Questions
What is the main difference between microservices and a monolith?
A monolith is a single deployable unit where all modules share the same process and database. Microservices split the application into independently deployable services, each with its own database and deployment pipeline. The tradeoff: monoliths are simpler to develop and debug, microservices enable independent scaling and deployment at the cost of distributed systems complexity.
Why did Amazon Prime Video switch back from microservices to a monolith?
Their video quality monitoring system used Step Functions + Lambda. At scale, the per-transition costs of moving data between services via S3 were prohibitive. Consolidating into a single process eliminated the network hop costs and reduced infrastructure costs by 90%. Frequent inter-service data transfer is a strong signal that services should be co-located.
What is a modular monolith?
A single deployable unit internally organized into well-defined modules with strict, enforced boundaries. Each module owns its data and exposes a clean API, but all modules run in the same process. You get simple debugging and ACID transactions while maintaining clean code organization that makes future service extraction practical.
When should you use microservices?
When multiple conditions apply simultaneously: teams larger than 50 engineers needing independent deployment, significantly different scaling requirements between components, regulatory data isolation mandates, or a polyglot technology requirement. These conditions rarely all apply for startups and small-to-medium teams.
How much do microservices cost compared to a monolith?
Real migration cost analyses from 2025 show microservices infrastructure runs 3.75x–6x more expensive than equivalent monoliths. The overhead comes from multiple databases, distributed tracing, service mesh, API gateway, and the platform team required to manage all of it. A 2025 Gartner report found 60% of teams regret microservices for small-to-medium apps.
What is a service mesh and do I need one?
A service mesh (Istio, Linkerd) handles service-to-service mTLS, load balancing, circuit breaking, and observability as infrastructure. You need one at 10+ services where managing these concerns individually becomes unworkable. For fewer services, HTTP clients with retry/circuit-breaker libraries (like Resilience4j or opossum) are simpler.
Build Better APIs for Any Architecture
Whether you choose a monolith or microservices, robust APIs are the foundation. Format and validate your API responses with the JSON Formatter. Test endpoint security with our URL Encoder. Inspect your JWT tokens with the JWT Decoder.
Open JSON FormatterRelated Articles
REST API Best Practices
Design the APIs that connect your services or expose your monolith.
Docker for Developers
Containerize and orchestrate microservices with Docker Compose.
CI/CD Pipeline Guide
Automate independent service deployments in microservices architecture.
GraphQL vs REST API
Compare API paradigms for service-to-service and client communication.