We Stopped Using Microservices and Nobody Misses Them
We spent about two years running eleven microservices for a product that, if I am being honest, could have comfortably lived in a single well-organized codebase the entire time. Then we merged everything back into one deployable application, and I want to explain why, because I think the standard microservices narrative skips over exactly the kind of team we were, and probably the kind of team a lot of people reading this actually are.
How we ended up with eleven services in the first place
Nobody made one bad decision that led here, it accumulated the way most architectural mistakes do, gradually and each individual step feeling reasonable at the time. A new feature area meant a new service, because that was the pattern established by the first couple of services and nobody questioned it. Team growth meant more services, because splitting by service felt like a clean way to give each new hire clear ownership boundaries. By the time I actually counted, we had eleven services for a team of nine engineers, which in retrospect is a number that should have set off alarms far earlier than it did. More services than engineers is a ratio that only makes sense at a scale where each service genuinely has enough complexity and traffic to justify a dedicated owner, and we were nowhere near that scale.
The actual costs, not the theoretical ones
The theoretical costs of microservices get discussed constantly, network latency, distributed tracing complexity, eventual consistency headaches. Those were real for us but honestly not the biggest issue. The biggest issue was much more mundane: the sheer number of places you had to look to understand a single user-facing feature. A typical checkout flow touched the orders service, the inventory service, the pricing service, and the notifications service, each with its own repository, its own deploy pipeline, its own set of environment variables, and its own on-call runbook. Onboarding a new engineer meant walking them through four separate codebases before they could meaningfully contribute to a single feature, and that ramp time, which we eventually measured, averaged around six weeks before someone could ship a cross-service change independently and confidently.
Deploys were the other silent tax. A feature that touched three services needed three coordinated deploys, and because each service had its own CI pipeline and its own release cadence, coordinating that sequencing correctly, this service first because it is backward compatible, then this one, then this one, consumed real engineering time every single week. We tracked it for a month out of curiosity and found we were spending roughly six hours a week collectively on deploy coordination alone, work that added zero direct value to the product, purely overhead from the architecture itself.
What actually triggered the decision to merge back
The moment that crystallized it for me was a bug where a price displayed correctly in the UI but the actual charge came out different by a few cents, caused by a subtle timing issue between the pricing service publishing an update and the orders service's cached copy of that price. Tracing that bug required correlating logs across three separate services, each with its own logging format at the time because standardizing had never made it to the top of anyone's priority list, and it took two engineers the better part of two days to find. In a monolith, that entire flow would have been a single function call stack, and the bug would likely have been visible in a stack trace within minutes.
That was not really about microservices being bad technology, plenty of companies operate them successfully at a scale where the tradeoffs make sense. It was about us paying the full architectural tax of a pattern designed to let large, independent teams deploy autonomously, while actually being nine people who mostly worked on the same roadmap and shipped features that touched multiple services simultaneously anyway. We never actually had the organizational shape that microservices are meant to solve for.
The migration back
Merging back was not a rewrite, which I think surprises people. We kept the internal module boundaries that had existed as separate services, orders, inventory, pricing, notifications, and turned them into clearly separated internal modules within one deployable application, communicating through function calls instead of HTTP requests.
// before: an HTTP call across service boundaries
const price = await fetch('http://pricing-service/api/price/' + sku).then(r => r.json());
// after: a direct, type-checked function call
import { getPrice } from '@/modules/pricing';
const price = await getPrice(sku);
The interfaces stayed almost identical, which meant the migration was mostly mechanical, replacing HTTP clients with direct imports and removing the serialization boundary, rather than a redesign of the actual business logic. It took about six weeks for the whole migration, spread across normal feature work rather than a dedicated freeze, and the module boundaries we kept from the service split turned out to be genuinely good internal architecture, we just no longer paid a network hop and a separate deploy pipeline to enforce them.
What got measurably better
Onboarding time for a new engineer to ship a cross-cutting change dropped from around six weeks to under two. Our deploy frequency actually went up, not down, because we went from eleven separate pipelines each deploying a few times a week to one pipeline deploying multiple times a day, since there was no more cross-service coordination tax slowing releases down. Total infrastructure cost dropped too, since we consolidated from eleven separately scaled services, several of which were massively over-provisioned relative to their actual traffic just to have headroom, into one application that scales as a unit.
When I would actually reach for microservices
I am not writing this as a blanket argument against the pattern. If you have genuinely independent teams that need to deploy on their own schedules without coordinating, if a specific part of your system has wildly different scaling characteristics that justify isolating it, video transcoding next to a mostly idle admin panel, for instance, or if you need strong technology or language isolation between parts of the system for real reasons, microservices solve real problems in those situations. What I would push back on is adopting the pattern as a default architectural starting point because it sounds like what a serious engineering team does. We treated microservices as a maturity signal for a while, and paying that tax without the underlying organizational or scaling need to justify it made everything slower, not more scalable. The monolith was never the naive choice. In our case, it was the one we had to relearn was actually correct.
I think part of why this was hard to admit at the time is that going back to a monolith can feel, socially, like a step backward within an industry culture that has spent a decade treating microservices as the mark of a serious, scaled engineering organization. Nobody writes a conference talk titled we merged our services back together and it went great with the same enthusiasm as the original migration talks, even though I would argue our retrospective story is at least as instructive as the original adoption story, maybe more so, because it required actually confronting a decision the team had collectively invested two years of identity into. If you are on a team quietly suspecting your architecture no longer matches your actual organizational shape, I would say the discomfort of admitting that out loud is a much smaller cost than the compounding tax of pretending otherwise for another two years.
Written by AIDToolStack Editorial
The AIDToolStack editorial team reviews developer tools, AI products, and MCP servers with hands-on testing and honest assessments. We use the tools we review every day in production.
References & Further Reading
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.