The Post-Microservices Recalibration
The industry is consolidating back toward monoliths. Modular monoliths are respectable again. "Right-sized services" has replaced "decompose everything" as the default recommendation. Senior engineers who built distributed systems at scale are publicly advocating for fewer services.
The conventional explanation: teams underestimated operational complexity. Network hops, distributed tracing, service mesh overhead, a hundred independent deployment pipelines. The tax was higher than the benefit.
That's true. But it explains the symptom, not the disease. And misdiagnosing the disease is why many teams are now making the same mistake in the opposite direction.
The Coupling Didn't Disappear. It Just Got Harder to See.
Take a common decomposition: an e-commerce team splits their system into user-service, auth-service, order-service, inventory-service, and notification-service. Clean vertical slices. Independent deployment. Textbook microservices.
Then the first real feature lands: "When an order is placed, deduct inventory, notify the user, and update their order history."
That's five services that need to coordinate on a single business transaction. The coupling was always there — it's inherent to the domain. Decomposing the system didn't remove it. It moved it from in-process function calls to synchronous HTTP chains. You can't follow a stack trace across a service boundary. The coupling became invisible — which made it much harder to manage.
This is the pattern that repeats across almost every failed microservices adoption: decomposition by technical layer (auth, notification, user) rather than by business capability boundary. The seams were drawn on the technical map, not the domain map.
A modular monolith with the same decomposition — auth module, notification module, user module — has identical coupling. It just expresses it through import dependencies instead of HTTP calls. The physical boundary changed. The logical structure didn't. Most teams consolidating back to monoliths right now are making this exact move without recognizing it.
Distribution Cost Is Not Linear
Most architectural thinking treats the cost of distribution as proportional to service count. More services, more complexity, higher cost. It's not that simple.
Distribution cost has two components that scale completely differently:
Fixed cost — paid once, regardless of how many services you have. Service discovery infrastructure, distributed tracing, centralized logging, inter-service authentication, deployment pipeline complexity, on-call runbooks that span service boundaries. You pay most of this the moment you have two services.
Interaction cost — paid per boundary crossed. Each network call adds latency. Each service boundary adds a failure mode. Each cross-service transaction adds consistency complexity.
Formally: Total cost = Fixed (paid once) + Interaction cost (paid per boundary crossed)
Now trace this across three architectures:
| Architecture | Fixed cost | Interaction cost | Net |
|---|---|---|---|
| Monolith | None | Controlled via module design | ✅ |
| Few services (3–10) | High | Low (few boundaries) | ❌ |
| Many services with correct boundaries | High but amortized | High but matched to org structure | ✅ (if org justifies it) |
The worst outcome — and the one most "right-sized" architectures land on — is the middle row. You've paid most of the fixed cost the moment you split. A team running five microservices is paying 80% of the operational overhead of a team running fifty. But they haven't gained the organizational autonomy or deployment independence that makes distribution worthwhile at scale.
The counterintuitive implication: the sweet spot for distribution is often many services or zero — rarely the three-to-ten range that feels like a reasonable compromise.
To be precise about when each end holds:
Many services works when: business domain boundaries are well understood, teams are genuinely independent with separate deployment cadences, and communication between services is predominantly async. Each service is someone's complete ownership, not a technical slice.
Zero services (modular monolith) works when: teams share a deployment cadence, transactional consistency across domains is frequent, and the organization is small enough that the coordination overhead of distribution exceeds its benefits.
The middle fails when: you've accepted the fixed cost without the organizational structure to amortize it, cross-domain transactions are common, or boundaries were drawn on the technical map instead of the domain map.
Why Teams Distributed in the First Place
If the math is this unfavorable, why did so many teams choose distribution? It was rarely technical necessity. It was almost always one of these:
Team boundary enforcement. When trust between teams is low, a network boundary is the most reliable contract enforcer. You can't accidentally call across a network boundary. You constantly accidentally call across a module boundary in a monolith. Distribution as organizational trust infrastructure — not technical infrastructure.
Deployment escape hatch. Teams under release pressure who couldn't negotiate a shared deployment cadence chose service decomposition to escape the coordination tax. The service boundary was a workaround for a process problem, dressed up as an architectural decision.
Premature scale signaling. "We expect to scale, therefore we must design for scale now." This one is pervasive in startups and cargo-culted in larger organizations. Netflix architecture gets adopted by teams with 1% of Netflix's traffic and none of its operational maturity. The architecture signals ambition. It doesn't serve the current system.
Resume-driven architecture. Uncomfortable to say, necessary to say. Microservices, service meshes, and distributed systems patterns are resume-building technologies. When engineers have architectural discretion and personal career incentives, they sometimes make choices that optimize for their next job rather than the current system's health. This is a principal-agent problem. No architectural pattern solves it.
The reason to understand which driver caused a specific distribution decision: consolidating to a monolith without addressing the original forcing function recreates the problem in a different form. If distribution was driven by team boundary enforcement, consolidation without reorganizing the teams just recreates the coordination chaos — inside the codebase instead of across service boundaries.
Modular Monoliths Only Work Under Conditions Nobody Talks About
The modular monolith is the current consensus recommendation, and it's sound — under conditions that are almost never stated explicitly.
A modular monolith with modules that mirror the old microservice decomposition has the same coupling problem, now expressed through import dependencies. The boundary is softer, the tooling doesn't enforce it, and delivery pressure will erode it. Within eighteen months of consolidation, the typical result is a big ball of mud that's harder to decompose than a clean monolith — because nobody can tell where the seams are anymore.
The discipline that makes it work is operational:
- No cross-module imports except through explicit public interfaces. Internal packages are internal. The build fails on violations.
- Explicit dependency graph enforced in CI. ArchUnit (Java), Go module graph checks, custom linting — whatever the language supports. If it's not automated, it will drift.
- No shared database tables across modules. Shared tables are the most common way module boundaries get silently violated. Each module owns its schema. Cross-module data access goes through the module's interface.
- Versioned internal APIs. Even if the interface never becomes a network call, treat it as if it might. This discipline makes future extraction into a service a refactoring, not a rewrite.
The last point is the key one: design module boundaries as service contracts, with services as an optional later extraction. You're not committing to distribution. You're preserving the option while paying none of the fixed cost. If the boundary proves unnecessary, you collapse it. If the team grows and the boundary needs to harden into a network call, you extract it without redesigning the interface.
Conclusion
The industry conversation has been almost entirely about topology — monolith vs. services, how many, how to right-size. Almost nobody is asking the question underneath: what unit of independent deployability do you actually need, and why?
Independent deployment is valuable when the cost of coordinating deployment across components exceeds the cost of maintaining the deployment boundary. That calculation depends on team structure, release cadence, feature velocity, and fault isolation requirements.
A team of six engineers releasing once a week has no meaningful use for service-level deployment independence. A platform team serving fifty product teams absolutely does. These are not the same problem. They should not have the same answer.
Most teams didn't fail at microservices because they distributed too early. They failed because they distributed without knowing what they were buying. And many are now consolidating without knowing what they're giving up.
The real output of the microservices era isn't a lesson about services. It's a forcing function for a question architects should have been asking from the beginning: what specific delivery or organizational problem does this boundary solve, and is a network call the right mechanism to solve it?
Distribution should be a deliberate purchase, made against explicit criteria. In most cases, it wasn't. The recalibration is healthy. But swapping one unexamined default for another isn't recalibration. It's the same mistake, pointed in the opposite direction.