We split a perfectly working app into eight microservices because it was 2023 and that's what "scalable" teams did. Eighteen months later, I was the one writing the RFC to put most of it back together.
Nobody likes admitting that. It feels like undoing someone else's work, or worse, undoing your own. But I'd rather write an honest postmortem than keep pretending the architecture was fine while three engineers spent half their week just keeping services talking to each other.
Why we split it up in the first place
The reasoning at the time sounded solid on paper. Independent deployability. Teams owning their own services. The ability to scale the busy parts of the app without scaling the whole thing. Every blog post about companies at our stage said some version of the same thing, and it was easy to believe we were behind if we hadn't already made the jump.
What none of those blog posts mentioned, or maybe just assumed was obvious, was the size of team those benefits actually require. We had twelve engineers. Splitting the app into eight services meant most services had one, maybe one and a half people who actually understood them end to end. The "independent teams owning independent services" model doesn't work when the teams don't exist yet. We built the architecture for a company we hoped to become, not the one we were.
What actually broke
The failures weren't dramatic. There was no single outage that made everyone say "okay, this was a mistake." It was slower and more corrosive than that.
Debugging turned into archaeology. A bug that used to mean stepping through one codebase now meant tracing a request across four services, three of which had inconsistent logging, to find out where the data actually went wrong. What used to take twenty minutes started taking half a day, and that was on a good day.
Local development got painful. Spinning up the full stack locally meant running eight services, each with its own environment variables, its own database, its own quirks about what port it wanted. New engineers took two weeks just to get a working local setup instead of two hours.
Deployments needed careful choreography. Services depended on each other in ways that weren't always obvious until deploy day. We'd ship a change to one service, break another, and spend the next hour figuring out which one actually needed to go out first. The "independent deployability" we split up for became its own coordination problem.
We were paying an infrastructure tax with no matching benefit. Eight services meant eight sets of CI pipelines, eight sets of monitoring dashboards, eight things that could each fail independently at 2am. We were paying operational complexity for scale problems we didn't have yet. Our actual traffic could have run comfortably on a single well built service.
Nobody could hold the whole system in their head. This one mattered more than any single technical issue. When something went wrong, there was no longer one person who could reason about the entire request path. Every incident started with a scramble to figure out who even understood the piece that broke.
The decision to go back
I didn't propose the monolith because microservices are bad. They're not. They solve real problems for teams at a certain scale with certain organizational boundaries already in place. I proposed it because we'd adopted the pattern before we had the problem it solves, and we were paying the cost of that mismatch every single week.
The RFC was blunt about it. We are not Amazon. We do not have twelve teams that each own a service end to end. We have twelve engineers who spend more time on service boundaries than on the product. Here is what a single, well organized codebase with clear internal module boundaries buys us back.
It wasn't a popular conversation at first. Some of that split had taken real effort, and nobody wants to hear that effort didn't pay off. But once we framed it as "what does our actual team, at our actual size, need right now" instead of "what did we lose face on," it got easier to agree on.
What I'd tell myself eighteen months earlier
Architecture decisions should follow the shape of your team and your actual traffic, not the shape of the team and traffic you're hoping to have in two years. If you can't clearly name which team owns which service, and mean it, you probably don't need separate services yet. You can build a monolith with clean internal boundaries, module by module, and split it later when a specific piece genuinely needs to scale or deploy independently. That split will be easier to do later, with real evidence behind it, than it is to undo now.
We didn't fail because microservices are wrong for us forever. We failed because we borrowed an architecture built for a different sized team and assumed the benefits would show up automatically. They don't. They show up when the organizational structure that justifies them already exists.
If your team is mid split right now and it's starting to feel heavier than it should, that feeling is worth listening to. It was for us, even if it took longer than I'd like to admit before I said it out loud.
Top comments (0)