For most teams, a modular monolith is the better default. It ships as one deployable unit, and the build enforces the module boundaries inside the code. Microservices on Kubernetes earn their cost once several teams need to release their parts independently and each team can own a service end to end. Until then, the extra infrastructure mostly adds systems to run.
What is the difference between a modular monolith and microservices?
Both styles divide an application by business capability. The difference is where those boundaries live once the code is running. The platform engineering page describes the work around this choice.
Microsoft’s architecture guide defines microservices as “small, independent, and loosely coupled components that a single small team of developers can write and maintain.” Each one “should implement a single business capability within a bounded context,” and each is managed as a separate codebase. A service persists its own data and talks to the others through well-defined APIs that keep its internals hidden.
A modular monolith uses the same idea of bounded contexts but ships every module in one deployable artifact. Modules call each other through explicit interfaces inside one process. The build enforces the boundary, for example with a dependency rule that fails when one module imports another module’s internals.
That gives a simple way to see the choice. Both styles decide how the code is organized. Microservices also commit you, today, to deploying each piece separately and running the machinery that makes separate deployment safe. A modular monolith makes the first decision now and leaves the second open.
Why do teams choose microservices?
The benefits in Microsoft’s guide are real. The first is agility. Because services deploy independently, “you can update a service without redeploying the entire application and roll back an update if something goes wrong.” The guide contrasts this with traditional applications, where a bug in one part “can block the entire release process” and stall unrelated features while the fix goes through integration and testing.
The second is team size. A service in its own codebase, owned by one small team, is something that team can understand fully and change without negotiating with anyone else. The guide also notes that services can be polyglot. They “don’t need to share the same technology stack, libraries, or frameworks.”
Look at what those benefits assume. Each one pays off when several teams’ releases get in each other’s way. A single team shipping one product rarely has its release blocked by another team’s bug, because there is no other team. For that team, independent deployment solves a problem it doesn’t have yet. Polyglot freedom is also a cost when the same few people have to maintain every stack.
What does the extra infrastructure actually cost?
Microsoft is direct that the change goes beyond code. The guide opens by saying that a successful microservices architecture “requires a fundamental shift in mindset,” and that it takes more than breaking an application into smaller services.
The guide then lists the components that appear next to the services themselves. Orchestration schedules and deploys services across nodes, recovers from failures and autoscales, and “a container orchestration platform like Kubernetes typically provides this functionality.” An API gateway becomes the entry point and handles cross-cutting concerns such as authentication and logging. Proxies such as Envoy and Nginx carry service-to-service traffic. Messaging platforms such as Apache Kafka or Azure Service Bus carry asynchronous events. Observability needs centralized logging plus distributed tracing, which “tracks requests across service boundaries.” Data becomes decentralized, because “each service owns its data and schema.”
Every item on that list is a system someone has to run and upgrade, and someone has to answer the pages when it breaks. Here are the two styles side by side.
| Concern | Modular monolith | Microservices on Kubernetes |
|---|---|---|
| Deployment | One artifact, one pipeline | One pipeline per service, plus the orchestrator |
| Entry point | The application itself | An API gateway in front of every request |
| Calls between parts | In-process calls through module interfaces | API calls between services, often through a proxy or a message broker |
| Data | One database, tables owned per module | Each service owns its data and schema |
| Following a request | One process’s logs and stack traces | Centralized logging and distributed tracing |
| Scaling | Scale the whole unit | Autoscale each service on demand |
| Release coupling | Every module ships together | Each service ships on its own schedule |
The right-hand column is what buys the independence from the previous section. If you don’t need that independence, you are paying for components whose main job is to make independent deployment safe.
When does Kubernetes stop paying for itself?
The cost side is mostly fixed once you have a cluster, a gateway, a broker and tracing. The benefit side grows with the number of teams that would otherwise block each other. That gives you a practical test.
Splitting into services tends to pay off when:
- Several teams regularly block each other’s releases in a shared codebase. This is the problem Microsoft’s agility argument addresses.
- One part of the system has a load profile very different from the rest and needs to autoscale on its own.
- Each team can carry deployment and on-call for what it ships.
- Someone owns the orchestrator and the tracing stack as a product, usually a platform team (see platform engineering vs DevOps vs SRE).
It stops paying when one team owns the whole product, or when most changes cut across module boundaries anyway. It also stops paying when cluster upgrades and gateway configuration are nobody’s job, because then they get done late or not at all.
There is a middle path for the case where only one piece genuinely needs to be separate. Microsoft points to managed platforms such as Azure Container Apps, which provide “managed orchestration and built-in scaling,” and says these tools “reduce deployment complexity and operational overhead.” Running one extracted service on a managed platform gives that piece independent deployment without you operating a Kubernetes cluster.
How do you keep the option to split later?
Write the monolith as if the module boundaries were service boundaries. Microsoft’s description of a service works as a checklist for a module. It owns one business capability and its data, and other parts reach it only through a defined interface.
In concrete terms, give each module a public interface and keep everything else internal. Keep each module’s tables private to it, so no other module queries them directly. This mirrors the guide’s point that data ownership “reduces cross-service dependencies.” Then add a build check that fails when one module imports another’s internals. With that in place, extracting a module later means putting it behind a network API. You don’t have to untangle shared tables first.
Before splitting anything, measure delivery. If lead time and change failure rate are healthy, the release-blocking problem that microservices solve probably isn’t there (DORA metrics: how to measure and improve them). If the numbers show teams blocking each other, extract the one module causing it, and decide early who owns the platform underneath (build or buy an internal developer platform). Once several services are running, promoting releases safely between environments becomes a job of its own (Argo CD and Kargo: promoting releases safely).
