If you've spent any time reading about software architecture, you've probably heard that microservices are the modern way to build software. Split everything into tiny, independent services, and you'll move faster, scale better, and sleep easier. That's the pitch, anyway.
But here's what experienced engineers know that blog posts often skip: most projects don't need microservices, and many are actively harmed by them. A well-structured monolith — one big application running as a single unit — is often simpler, faster to develop, and easier to reason about. Let's look at why the humble monolith deserves more respect than it gets.
Simplicity Value: One System, Fewer Headaches
Imagine you're building a bookshop application. You need user accounts, a product catalog, a shopping cart, and a checkout system. In a monolith, all of this lives in one codebase, deployed as one application. You run it locally with a single command. You debug it by stepping through code in one place. When something breaks, you look at one set of logs.
Now picture that same bookshop split into five microservices. Suddenly you need service discovery so they can find each other, a message queue so they can talk reliably, separate deployment pipelines for each one, and monitoring dashboards for every service. Your simple bookshop now requires infrastructure that looks like it belongs at a Fortune 500 company. Every new service you add multiplies your operational burden — more things to deploy, monitor, and keep running.
For a small team or a new project, this overhead is a real cost. Instead of spending your time building features that users care about, you're wrestling with infrastructure. A monolith lets a team of two or three developers move remarkably fast because there's only one thing to understand, one thing to deploy, and one thing to fix when it breaks. Simplicity isn't a limitation — it's a superpower.
TakeawayComplexity has a cost, and it compounds. Before splitting a system apart, ask whether the complexity of managing many services outweighs the complexity of managing one.
Transaction Boundaries: Keeping Your Data Honest
Let's say a customer places an order. You need to charge their payment, reduce the inventory count, and create an order record. These three things need to happen together — if the payment goes through but the inventory doesn't update, you've sold something you don't have. In a monolith, this is straightforward. You wrap all three operations in a database transaction, which guarantees they either all succeed or all fail. One database, one transaction, no ambiguity.
In a microservices world, each of those operations might live in a different service with its own database. Now there's no single transaction to protect you. Instead, you need patterns like sagas or eventual consistency — techniques where each service does its part and you build compensation logic for when something fails halfway through. It works, but it's genuinely difficult to get right, and the failure modes are subtle and hard to test.
This isn't a theoretical problem. Data inconsistency bugs in distributed systems are some of the hardest issues to diagnose and fix. They happen intermittently, they depend on timing, and they can silently corrupt your data for days before anyone notices. For many applications — especially ones where data accuracy matters more than extreme scalability — a monolith's ability to use simple, reliable transactions is an enormous advantage.
TakeawayWhen multiple things must happen together reliably, keeping them in one system with one database is dramatically simpler than coordinating across many. Don't distribute your data until you have a strong reason to.
Evolution Path: Start Together, Split When You Know Enough
One of the biggest mistakes teams make is designing microservices before they truly understand their problem. Where do you draw the lines between services? You have to decide upfront, and if you get the boundaries wrong, you'll spend months refactoring across services — which is far more painful than refactoring within a single codebase. Bad service boundaries create tightly coupled distributed systems, which give you all the downsides of both architectures and the benefits of neither.
A monolith gives you time to learn. As you build features and watch how users actually use your software, natural boundaries emerge. You start to see which parts of the system change together, which parts need to scale independently, and which teams need autonomy. Those are signals that tell you where to split — and they only become visible after you've been building for a while.
Martin Fowler calls this the "monolith first" approach, and it's pragmatic wisdom. Build a well-structured monolith with clear internal modules. When a specific part of the system genuinely needs to scale independently or be deployed separately, extract it into its own service. You're making an informed decision based on evidence, not a speculative one based on hype. Most successful microservice architectures started as monoliths that were carefully decomposed over time.
TakeawayYou can always extract a service from a well-structured monolith. It's much harder to merge poorly designed services back together. Start simple, learn your domain, and split only when the evidence tells you to.
Microservices solve real problems — but they're solutions designed for organizations that have outgrown what a monolith can offer. For most teams, most of the time, a well-organized monolith is the faster, safer, and more productive choice.
The best architecture isn't the one with the most impressive diagram. It's the one that lets your team deliver value to users with the least unnecessary complexity. Sometimes, that means one big system is exactly right.