Everyone seems to be chasing microservices these days—promises of independent teams, targeted scaling, and fault isolation make them sound irresistible. It’s been said you should only reach for microservices if you have a really good reason to do so. Otherwise, you risk over‑engineering your system and ending up with a distributed monolith—a setup that’s even more brittle and complex than a traditional monolith.
Riding in to save the day is the “modular monolith”, an architecture that delivers microservices‑style modularity within a single in‑process deployment. In this blog post, I’ll walk you through why it can be ideal for many (or even most) software projects, and how you can decide when it fits your needs.
The Monolith Spectrum
There are fundamentally two different kinds of monoliths—good ones and bad ones. The good ones are easy to maintain, perform well, and offer some scaling options. The bad ones are difficult to change, brittle, slow, and don’t scale well. We could just call these “bad” and “good”, but they are often referred to with different terminology—”ball of mud” monoliths are the bad ones, and “modular” monoliths and the good ones.
Ball of Mud
A “ball of mud” monolith is the classic anti‑pattern: a codebase with tangled dependencies where a small change breaks other parts of the system. Every feature tweak risks customer frustration and regression bugs, because components are tightly coupled and poorly organized. Trying to maintain it is like trying to organize a ball of mud.
Modular Monolith
At the other end sits the modular monolith. It’s still a single deployable unit, but its internal structure is divided into clear, loosely-coupled modules. You compile and release the whole application at once, yet you can change one module without fear of inadvertently breaking another. This yields faster feedback loops, simpler local development, and full transactional consistency—all without distributed complexity.

Distributed Monolith
Microservices promise a lot, but distributed systems are hard. If you spin up services that still share databases or call one another synchronously, you haven’t gained anything—you’ve merely spread your ball of mud across the network. The result is a distributed monolith: physically distributed components that remain logically monolithic, tightly coupled, and brittle.
Graphing Monoliths and Microservices
Fundamentally what defines all of this architectures is where they fall on two axes:
- How modular, or logically distributed, the code is and
- How physically distributed the parts of the application are deployed.
You can see that clearly on the chart below.

Microservices: Only with a “Really Good Reason”
Microservices are dangerous unless you have a “really good reason” to build them—and in my experience, most teams don’t. In fact, based on my unscientific survey, I’d estimate that well over 90% of microservice projects end up as distributed monoliths.
When should you consider a microservices architecture? Some valid reasons for microservices include:
- Organizational scale: dozens of teams needing clear ownership boundaries and the ability to release on independent cycles from other teams.
- Diverging non‑functional requirements: wildly different tech stack, up-time, performance, latency, or storage needs for different parts of your application.
- Regulatory or data‑sovereignty constraints: legal isolation of data for different parts of you data.
Absent these, you’re better off with a modular monolith. Also, microservices come with one very important trade-off. Building microservcies can give you high-availability, but it requires you to accept eventual consistency.

Eventual consistency means each service updates its own data store and shares changes asynchronously with the others, so data may be briefly out of sync but will eventually converge across the system. This model boosts availability and scalability by avoiding distributed transactions, relying instead on resilient messaging and retry mechanisms to reconcile differences over time.
If you don’t have one of the “really good reasons” above, and you can’t deal with eventual consistency, microservices will not work well for you, and you should consider a modular monolith instead.
Advantages of the Modular Monolith
A modular monolith is a single deployable application that’s architected with clear, well‑defined internal modules rather than a tangled “ball of mud.” Although you still compile and release the entire system as one unit, each module has its own boundaries and low‑coupling interfaces, so you can make changes in one area without fear of breaking functionality elsewhere.
This approach combines the simplicity and immediate consistency of a traditional monolith with the maintainability and clarity often attributed to microservices—without the operational overhead of distributed systems.
This approach has several advantages over microservices, including:
- Simplicity: One build, one test suite, one deployment pipeline.
- Immediate consistency: A single datastore avoids the eventual consistency dilemmas of distributed systems.
- Easier refactoring: Clear module boundaries encourage safe, incremental code evolution.
- Onboarding & collaboration: Small teams can understand the whole system without navigating dozens of repositories.
By starting with a modular monolith, you keep complexity in check and buy time to prove your architecture choices.
Evolving a Modular Monolith to Microservices
Once you’ve built a modular monolith, it’s not too much work to split all of parts of it into microservices. If you decide you have a “really good reason” to move your entire application to microservices, you’re already already well on your way.
Evolving a Ball of Mud to a Modular Monolith
Moving a ball of mud to a modular monolith can be a time-consuming process, and it’s best to split it up, agile-style, into smaller pieces. To be able to deploy this while it’s still partially migrated, you can use the “strangler fig” pattern, which involves putting a facade layer, typically a reverse proxy, in front of your legacy and modern applications. Use the facade to forward requests to either application, depending on where the feature or endpoint in question resides. At first, this will be primarily your legacy application, but eventually, you’ll move more of your functionality to the modern app, eventually allowing you to even get rid of the facade and shut down the legacy app.

Conclusion
While microservices can deliver powerful benefits in the right circumstances, they are far from a silver bullet for all projects; in fact, attempting them without a compelling need often leads to fragile, distributed monoliths rather than robust, independent services.
In most cases, beginning with a modular monolith—an internally decoupled, single deployable application—offers the best of both worlds: the operational simplicity and immediate consistency of a monolith combined with the maintainability and clear module boundaries of microservices.
Only once you face genuine requirements for independent scaling, divergent performance or security constraints, or organizational scale should you consider extracting services; by starting simple and staying modular, you can ensure that any move to microservices is purposeful and sustainable.
At Trailhead, we have a great deal of experience building both microservices and modular monoliths. Contact us and we can help you decide with one is right for your project.


