In May 2023, an Amazon Prime Video engineering team published a post that quietly detonated a decade of conventional wisdom: they had taken one of their monitoring services off a distributed serverless-microservices design, moved it back into a single process, and cut its running cost by ninety percent. The internet argued about it for a year. By the start of 2025—architecture-planning season, when teams sketch the system they will live inside for the next three years—"monolith-first" and "modular monolith" had gone from contrarian takes to the default advice you hear from people who have actually run both. The pendulum did not swing because monoliths got better. It swung because the industry finally counted the bill for splitting too early. As Hrishikesh Baidya, our CTO, frames it for every founder who opens with "we want microservices": "Microservices are an answer to an organizational question. If you can't state the question, you don't have the answer." At Softechinfra we have shipped both architectures across client projects, and the teams that struggle are almost never the ones who "chose wrong"—they are the ones who distributed a system before they had a reason to.
What Microservices Actually Solve
Strip away the conference talks and microservices solve exactly one class of problem well: letting many teams deploy independently without coordinating a single release. If you have eight squads who each want to ship on their own cadence, owning their own data and their own on-call, service boundaries that match team boundaries are worth their weight. That is Conway's Law working for you instead of against you.
What microservices do not solve is everything they get sold for. They do not make code cleaner—a tangled monolith becomes a tangled distributed system with network calls between the tangles. They do not automatically improve performance—an in-process function call measured in nanoseconds becomes a network round trip measured in milliseconds, with serialization, retries, and timeouts bolted on. And they do not reduce complexity; they relocate it from the code, where a compiler and a debugger can help you, into the network, where nothing can.
The corollary matters as much as the rule: for a single team, microservices are nearly all cost and almost no benefit. You inherit distributed transactions, eventual consistency, service discovery, distributed tracing, and a deployment topology that needs its own platform team—while still coordinating every release, because the services are coupled anyway. You paid the full distributed-systems tax and got none of the organizational independence it buys.
The Hidden Costs of Premature Distribution
The seductive thing about microservices is that the costs are invisible at design time. On a whiteboard, ten neat boxes with arrows look more organized than one big box. The bill arrives later, in operations.
The local call becomes a network call. Inside a monolith, one module invoking another is a function call: instant, transactional, and impossible to half-fail. Across a service boundary it is a network request that can be slow, can time out, can succeed on the server but fail to deliver the response, and can return after you already gave up. Every boundary you draw is a place latency and partial failure now live.
Transactions stop being transactions. A monolith wraps "charge the card, create the order, decrement inventory" in one database transaction—it all happens or none of it does. Split those across three services and you have lost atomicity. Recovering it means sagas, compensating actions, and idempotency keys: a large amount of careful, error-prone code to re-create something the database gave you for free.
Debugging turns into archaeology. A stack trace that used to point at one line now spans five services, three queues, and two languages. You cannot reproduce the bug locally because "local" is now a docker-compose file with eleven containers. Distributed tracing becomes mandatory infrastructure, not a nice-to-have.
The Decision Framework
The choice is not binary and it is not permanent. Walk these signals honestly before you draw a single service boundary.
- How many teams deploy independently? One team, or one team you can imagine for the next year: stay a monolith. The single strongest argument for splitting is organizational, not technical.
- Do you know your domain boundaries yet? Pre-product-market-fit, boundaries move weekly. Splitting now welds today's guesses into network contracts that cost a quarter to renegotiate. Modules are free to redraw; services are not.
- Does one component have a genuinely different scaling or runtime profile? A CPU-bound video transcoder, an ML inference endpoint, or a memory-hungry report builder is a legitimate reason to peel off one service—while the rest stays monolithic.
- Can you operate it? If you do not already run centralized logging, tracing, and per-service pipelines, that is the work microservices create before they create value. Be honest about whether you have the people.
- Is there a compliance or blast-radius reason? Isolating a payments or PII component behind a hard boundary can be worth the cost even at small scale. That is a real reason; "it feels more scalable" is not.
If you answered "one team, boundaries still moving, no operational platform" to most of these, the framework has spoken: build a monolith, and build it well. We make the same kind of explicit, written architecture call before any client build—the discipline mirrors what we describe in our SaaS architecture patterns guide, where the most expensive mistakes are always the decisions nobody consciously made.
The Modular Monolith: The Middle Path Most Teams Want
The false choice—big-ball-of-mud monolith versus sprawling microservices—skips the option that fits the overwhelming majority of products: the modular monolith. One deployable unit, one database, one repository, one release. But inside, the code is organized into modules with explicit, enforced boundaries: each module owns its data, exposes a public interface, and is forbidden from reaching into another module's tables.
You get the operational simplicity of a monolith—one thing to deploy, debug, and run in a transaction—with the internal discipline that makes a future split tractable. The key word is enforced. Folder structure alone decays; people take shortcuts under deadline. Use build-time tooling to fail the CI when a module reaches across a boundary, the same way a type checker fails a bad call. The boundary has to bite, or it is just a suggestion.
Modules Own Their Data
Each module reads and writes only its own tables. Cross-module access goes through a published interface—never a foreign key into someone else's schema. This single rule is what makes a later extraction a refactor instead of a rewrite.
Communicate Through Interfaces
Modules call each other through explicit public methods or in-process events, not by importing internals. When a module later becomes a service, that interface becomes its API and almost nothing else has to change.
Enforce Boundaries in CI
Architecture tests or module-dependency linters fail the build on illegal imports. Boundaries that aren't enforced by tooling erode in a sprint. Make the wrong call impossible to merge.
Stay One Deployable
One process, one database, one transaction, one pipeline. You keep atomic writes and a stack trace that points at one line—while preserving the seams to split later if, and only if, the organization demands it.
A well-built modular monolith is the cheapest insurance you can buy against both failure modes. If you never need to split, you have a clean, fast, debuggable system. If you do, the modules are already the services—you are extracting along seams you designed, not hacking through couplings you discovered too late.
How to Compare Them Honestly
| Factor | Monolith / Modular Monolith | Microservices |
|---|---|---|
| Best team size | One to a few teams sharing a release | Many teams deploying independently |
| Operational overhead | Low—one thing to run and debug | High—mesh, tracing, per-service pipelines |
| Transactions | Native ACID across the system | Sagas and eventual consistency |
| Independent scaling | Scale the whole unit (usually fine) | Scale each service separately |
| Failure modes | In-process; reproducible locally | Partial, network-level, distributed |
| Cost of getting it wrong | Refactor a module in a sprint | Renegotiate network contracts for a quarter |
The honest reading of this table is that microservices win exactly one row decisively—independent deployment by many teams—and that row is the only one that justifies the rest. If it does not describe you, the monolith column is not a compromise. It is the right answer.
When Splitting Genuinely Pays
None of this is an argument that microservices are wrong. They earn their cost when the organization, not the codebase, demands them. The clean way to get there is the strangler-fig approach: keep the monolith running, peel off one well-bounded capability into a service, route traffic to it, prove the operational model works, and only then consider the next one. This is the same incremental, de-risked philosophy we apply to legacy work in our rewrite-versus-refactor framework and our legacy modernization guide—never a big-bang rewrite, always a reversible step.
On one client platform we built for ChipmakerHub, a B2B marketplace for the semiconductor supply chain, the system began life—correctly—as a modular monolith. The split came later and surgically: a heavy catalog-matching and search workload had a genuinely different scaling profile from the transactional core, so we extracted that one capability behind the public interface its module already exposed, fronted by an API gateway while the rest stayed monolithic. Because the seam existed by design, the extraction was a focused two-week project rather than an architectural rescue. When a tenant boundary later joined the picture, the patterns in our multi-tenant architecture guide slotted in cleanly because the data ownership rules were already there.
The Prime Video story endured precisely because it was not really about Amazon. It was a reminder that "microservices" stopped being a synonym for "good engineering" and went back to being what it always was: a specific tool with a specific, organizational job. Pick it on purpose, for the question it answers, or not at all.
Not Sure Which Architecture Your Product Needs?
We help founders and product teams make architecture decisions they won't regret in a year—monolith-first builds, modular monoliths with enforced boundaries, and careful service extraction only when it pays.
Talk to Our Engineering Team →
