Why You Should Start With a Monolith
The Microservices Trap
Every week we talk to a founder who's 6 months into a microservices build and hasn't shipped a single feature to production. The architecture diagram looks impressive. The Kubernetes cluster is humming. But customers? Still waiting.
Here's the uncomfortable truth: you don't need microservices yet.
When Monoliths Win
A well-structured monolith gives you:
- Speed to market — one repo, one deploy, one mental model
- Simpler debugging — stack traces that actually make sense
- Lower ops burden — no service mesh, no distributed tracing, no 47 Helm charts
- Easier refactoring — rename a function, not a contract
// This is fine. Really.
export class OrderService {
constructor(
private readonly db: Database,
private readonly payments: PaymentGateway,
private readonly inventory: InventoryTracker,
) {}
async placeOrder(input: PlaceOrderInput) {
const order = await this.db.orders.create(input);
await this.payments.charge(order);
await this.inventory.reserve(order.items);
return order;
}
}The Key: Modular Boundaries
The trick isn't avoiding structure — it's avoiding premature distribution. Keep your code modular inside the monolith:
- Define clear module boundaries — each domain gets its own directory
- Use interfaces between modules — don't reach into another module's internals
- Keep your database schema clean — foreign keys are fine, shared tables are not
- Write integration tests at module boundaries — these become your future API contracts
When to Actually Split
You've earned microservices when:
- A specific module needs to scale independently (not theoretically — actually)
- Teams are stepping on each other in the same codebase daily
- You need different deployment cadences for different parts of the system
- A module has fundamentally different runtime requirements (GPU, memory, etc.)
"If you can't build a well-structured monolith, what makes you think you can build a well-structured distributed system?" — Simon Brown
The Migration Path
When you do split, the modular monolith makes it straightforward:
- Extract the module into its own service
- Replace the in-process interface with an API call
- Add a message queue for async operations
- Deploy independently
The integration tests you wrote? They become your API contract tests with minimal changes.
Bottom Line
Ship the monolith. Validate the business. Split when the pain is real, not theoretical. Your future self (and your runway) will thank you.