Skip to main content

Command Palette

Search for a command to run...

Why we moved our microservices back into a monolith

We built dozens of tiny services, but the cost of separate pipelines, monitoring and network latency eclipsed speed gains, so we merged into a modular monolith.

Updated
3 min readView as Markdown
L
I build systems that run reliably at scale and agents that work autonomously. Currently focused on the intersection of AI tooling and developer infrastructure.

Originally published on lavkesh.com


In 2022 I watched a team I mentor tear down a dozen services and ask why we spent so much time fighting our own infrastructure instead of shipping code.

The impulse to carve out a new service became the default answer whenever a feature grew a bit larger, even when the code could have lived happily inside the same process.

What we didn’t anticipate was the hidden tax: every service demanded its own CI pipeline, its own Prometheus scrape, its own on‑call rotation, and every call turned into a network hop that added milliseconds to latency.

For example, we had a service that handled authentication, which was called by almost every other service. It had its own pipeline, monitoring, and on-call rotation. But when we looked at the traffic, we saw that it was only handling a few hundred requests per second. We realized that we could easily move it back into the monolith and eliminate the extra overhead.

After a few months the operational burden outweighed the theoretical benefits, and I started looking at the old monolith we’d once called a legacy monster.

A well‑designed monolith with clear module boundaries and strict dependency rules can give you most of the isolation microservices promise, minus the extra network and ops layers. DHH called it the Majestic Monolith, Spring later packaged the idea as Modulith, and the .NET world has similar patterns.

In our case, we used a combination of Java modules and clear interface definitions to keep the internal architecture clean and maintainable. We also made sure to follow strict dependency rules to avoid coupling between modules.

Those patterns let you deploy the whole thing as a single unit, run unit tests across module boundaries, and still keep a clean internal architecture that developers can reason about.

Microservices still make sense when you have dozens of autonomous squads that need to ship on completely separate cadences, when a subset of traffic must scale to millions of requests per second, or when compliance forces physical isolation.

In most of the companies I’ve seen, those conditions are rare; the majority of teams end up paying for pipelines and monitoring they never use.

To move back, I first map which services talk to each other constantly, then ask if they ever needed independent scaling. I draw a consolidation boundary and apply the strangler‑fig pattern in reverse, pulling code into the monolith piece by piece until the old services can be retired.

More from this blog

B

Blissful Bytes

622 posts