Microservices vs Monolith: When to Split
Microservices vs Monolith: When to Split
The microservices vs monolith debate is usually settled by fashion, not engineering. The honest answer: most teams should start with a monolith, and most teams that split should have waited. This guide gives you the decision framework, the real costs of distribution, and the safe path when a split is genuinely justified.
The Real Costs of Microservices
Microservices trade a simple problem (one codebase, one deploy) for a hard one (distributed systems). The costs are not theoretical:
- Network failures: every service call can fail, time out, or return garbage. You now need retries, timeouts, circuit breakers, and idempotency.
- Data consistency: no more transactions across tables. You need sagas, outbox patterns, and eventual consistency.
- Operational complexity: N services means N deploys, N log streams, N dashboards, N alerting rules.
- Debugging: a request now spans multiple services, each with its own logs and traces.
- Team coordination: API contracts become political artifacts.
The rule of thumb: a microservice should be justified by a concrete benefit, not by architecture aesthetics.
When a Monolith Is the Right Answer
A monolith is the right answer for:
- Startups and small teams: speed of iteration beats everything else.
- Early-stage products: the domain boundaries are not yet known; a monolith lets you discover them.
- Teams under 10-15 engineers: the coordination overhead of microservices exceeds the benefit.
- Simple domains: CRUD apps do not need distributed systems.
The modular monolith is the modern compromise: one deployable, but with hard module boundaries enforced by the compiler or linter.
// A modular monolith — boundaries enforced in code, not in deploys
// modules/orders/index.ts
export { OrdersService } from "./orders.service";
export type { Order, OrderStatus } from "./order.model";
// modules/orders/orders.service.ts — internal, not exported
# Enforce boundaries with a lint rule
npx eslint --rule 'no-restricted-imports: ["error", { "patterns": ["modules/orders/*"] }]' src/modules/payments
When Microservices Are Justified
Microservices earn their cost in specific situations:
- Independent scaling: one workload needs 100 replicas while another needs 2. Separate deploys let each scale independently.
- Independent deploy cadence: a team ships 10 times a day while another ships weekly. Separate deploys remove the coupling.
- Isolation of failure: a crash in one service must not take down the whole product.
- Team autonomy at scale: 50+ engineers on one codebase create merge conflicts and release trains. Service ownership restores autonomy.
- Different tech stacks: genuinely justified rarely, but real (e.g., a Go service for high-throughput ingestion next to a Node API).
- Compliance or security boundaries: PCI data isolated in its own service with its own access controls.
The Decision Framework
Ask these questions in order:
| Question | If yes, lean toward... |
|---|---|
| Team smaller than ~15 engineers? | Monolith |
| Domain boundaries still unclear? | Monolith |
| One workload needs independent scaling? | Microservices |
| Teams need independent deploy cadence? | Microservices |
| Failure isolation is a hard requirement? | Microservices |
| Compliance requires hard data boundaries? | Microservices |
| None of the above? | Modular monolith |
How to Split Safely
If you split, do it incrementally. The strangler fig pattern: extract one service at a time, keeping the monolith healthy throughout.
Step 1: Find the seam. Look for modules with stable boundaries, few cross-dependencies, and clear ownership:
# Find the module with the fewest cross-module imports
grep -r "from '../" src/modules/ | awk -F: '{print $1}' | sort | uniq -c | sort -n
Step 2: Extract the data first. The database is the hardest coupling. Move the service's tables behind an API before moving the code:
Before: monolith ──► orders table (shared DB)
After: monolith ──► orders-service API ──► orders DB (owned)
Step 3: Extract the code. Move the module to a new service, expose it over HTTP or gRPC, and point the monolith at the new endpoint.
Step 4: Handle consistency. Replace the shared transaction with a saga or outbox pattern:
// Outbox pattern — write the event in the same transaction as the state change
await db.transaction(async (tx) => {
await tx.order.update({ id, status: "paid" });
await tx.outbox.create({ type: "order.paid", payload: { id } });
});
// A relay worker publishes outbox rows to the message broker
Step 5: Add the distributed systems toolkit. Retries, timeouts, circuit breakers, and tracing are not optional:
// Circuit breaker around a service call
const breaker = new CircuitBreaker({
failureThreshold: 5,
resetTimeoutMs: 30_000,
});
async function callOrdersService(payload: unknown) {
return breaker.execute(() =>
fetch("http://orders/orders", {
method: "POST",
body: JSON.stringify(payload),
signal: AbortSignal.timeout(2_000),
}),
);
}
The Anti-Patterns
- Splitting for "scalability" you do not have: if you are not at the scale where one service needs independent scaling, you are paying for nothing.
- Splitting by technical layer (frontend, backend, DB): this creates distributed monoliths with all the cost and none of the benefit.
- Splitting without data ownership: if services share a database, you have not split anything.
- Splitting to use a new tech stack: the stack is rarely the bottleneck.
Implementation Checklist
- Confirm a concrete benefit justifies the split
- Start with a modular monolith and enforced boundaries
- Extract data ownership before code
- Split one service at a time (strangler fig)
- Add outbox/saga for consistency
- Deploy retries, timeouts, circuit breakers, tracing
- Keep the monolith healthy during extraction
- Revisit the decision: is the split paying for itself?
MatterAI builds frontier AI infrastructure for engineering teams — from inference-optimized models to autonomous coding agents and agentic code reviews.
Explore what we're building:
- Orbital IDE — Autonomous AI coding agent with background agents and deep codebase memory
- AI Code Reviews — Agentic pre-commit reviews across GitHub, GitLab, and Bitbucket
- Axon Models — Frontier-grade reasoning models at 70% lower inference cost
Share this Guide:
More Guides
Local LLMs in Your IDE: Connecting Ollama to Coding Agents and Autocomplete
Wire local models into VS Code, JetBrains, Cline, Continue, and Aider via the OpenAI-compatible API. Covers model routing, context budgets, tool calling with small models, and when a local model is the right choice for the job.
15 min readBuilding a Self-Hosted AI Stack: Ollama, Open WebUI, and Local RAG
Stand up a fully self-hosted AI stack on a single machine: Ollama for inference, Open WebUI as the chat interface, local embeddings for RAG, and a reverse proxy for secure access. No cloud dependency, no data leaving your network.
17 min readTop 5 Open-Source Coding Models to Run on Your Mac (2026)
The best local coding models for Apple Silicon in 2026, ranked by quality per gigabyte of unified memory. Covers qwen3-coder, devstral, gpt-oss, and more with real pull tags, sizes, and context windows.
14 min readRunning LLMs Locally: GGUF, Quantization, and Memory Planning
Learn the GGUF format, the quantization ladder from Q2 to FP16, and the exact memory math for running models on Apple Silicon and NVIDIA GPUs. Includes Ollama and llama.cpp tuning for KV cache and context.
15 min readOllama vs vLLM vs llama.cpp: Choosing the Right Local LLM Runtime
Compare the three dominant local LLM runtimes on architecture, throughput, hardware, and deployment context. Includes benchmark data, a decision framework, and a migration path from Ollama to vLLM.
16 min readContinue Reading
Local LLMs in Your IDE: Connecting Ollama to Coding Agents and Autocomplete
Wire local models into VS Code, JetBrains, Cline, Continue, and Aider via the OpenAI-compatible API. Covers model routing, context budgets, tool calling with small models, and when a local model is the right choice for the job.
15 min readBuilding a Self-Hosted AI Stack: Ollama, Open WebUI, and Local RAG
Stand up a fully self-hosted AI stack on a single machine: Ollama for inference, Open WebUI as the chat interface, local embeddings for RAG, and a reverse proxy for secure access. No cloud dependency, no data leaving your network.
17 min readTop 5 Open-Source Coding Models to Run on Your Mac (2026)
The best local coding models for Apple Silicon in 2026, ranked by quality per gigabyte of unified memory. Covers qwen3-coder, devstral, gpt-oss, and more with real pull tags, sizes, and context windows.
14 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
