Microservices vs Monolith
Two fundamentally different ways to structure an application. A monolith is a single deployable unit where all components run in the same process. Microservices split the application into independently deployable services, each owning its domain and data.
Monolith vs Microservices — Failure Isolation
The Monolith
A monolith puts everything in one deployable unit. Your user service, payment service, notification service, and order service all live in the same codebase, share the same database, and deploy together as one artifact. You run one command and the whole application starts.
This is not a dirty word. Monoliths are simple to develop locally, simple to test end-to-end, and simple to reason about — you can ctrl+click your way from an HTTP handler to the database call in a single project. Some of the most successful and highest-traffic systems in the world ran as monoliths for years. Amazon was a monolith. Shopify still runs what they call a "modular monolith." Stack Overflow serves hundreds of millions of requests per month from a remarkably lean monolithic architecture. Basecamp has written publicly about choosing to stay monolithic as a deliberate, principled decision.
The Microservices Architecture
Microservices split the application along business domain boundaries. Each service owns its own data store, its own deployment pipeline, and can be written in whatever language best fits the job. Services communicate over HTTP/REST, gRPC, or message queues.
The promise is that teams can move independently. The team owning the payments service can deploy on Friday without coordinating with the team owning the notifications service. At Netflix, this is taken to the extreme — they run over 700 microservices, and the organisation of those services roughly mirrors the organisation of their engineering teams. Conway's Law in action: your system architecture tends to mirror your communication structure.
The Hidden Costs of Microservices
The pitch sounds good. The reality has sharp edges.
Network latency. What was a function call (nanoseconds) is now a network call (milliseconds). A single user-facing request that touches 10 services accumulates 10 network hops. Under load, this compounds with retries and timeouts.
Distributed tracing. When a request fails, where did it fail? Which of the 10 services is throwing the error? You need distributed tracing infrastructure (Jaeger, Datadog APM, AWS X-Ray) just to follow a request across service boundaries. Debugging is an order of magnitude harder than searching a single log stream.
Service discovery. Services need to find each other. In a monolith, a function call always works. In microservices, you need a service registry (Consul, Kubernetes DNS) so Service A can locate Service B at runtime, even as instances scale up and down.
Data consistency. In a monolith, a database transaction can span multiple domains — charge the payment and update the order in one atomic commit. In microservices, each service owns its database. There are no cross-service transactions. You're in eventual consistency territory, which means sagas, compensating transactions, and accepting that the system may be temporarily inconsistent.
Operational complexity. Ten services means ten deployment pipelines, ten sets of Kubernetes configs, ten monitoring dashboards, ten alert runbooks, ten sets of secrets to rotate. The operational overhead scales with the number of services. Small teams drown in this.
Integration testing. Unit testing a service is fine. But testing that Service A, B, and C work correctly together requires all three to be running. Contract testing (Pact) helps, but it's another tool in a growing toolchain.
When a Monolith Is Right
The monolith is almost always the right starting point. If you're in the early stages of a product, your domain isn't fully understood yet — the boundaries you draw for microservices today will be wrong, and refactoring across service boundaries is far more painful than refactoring within a single codebase.
A small team — under ten engineers — will spend more time managing the overhead of microservices than building product. If you haven't hit real scaling problems yet, you're solving imaginary ones. Speed of iteration matters most when you're finding product-market fit, and monoliths win on that dimension every time.
When Microservices Make Sense
Microservices earn their complexity when teams are large enough that the monolith itself becomes the bottleneck. When ten teams are all committing to the same repo, merge conflicts and deployment coordination slow everyone down. Splitting by team boundary (which usually maps to domain boundary) unblocks independent velocity.
They also make sense when different parts of the system have genuinely different scaling requirements. Video transcoding is compute-intensive and needs to scale differently than user authentication, which is mostly I/O-bound. Running them as separate services lets you right-size the infrastructure for each independently.
Finally, different release cadences are a real signal. If the payments team needs to deploy three times a day and the reporting team deploys twice a month, coupling them in the same deployment unit creates unnecessary friction.
The prerequisite for all of this is that your domain is well understood and your service boundaries are clear. Premature microservices drawn around fuzzy boundaries are worse than a monolith — you get all the complexity with none of the organisational benefits.
The Modular Monolith
There's a middle ground that doesn't get enough attention. A modular monolith is a single deployable unit with enforced internal boundaries. Each domain (users, payments, orders) is a module with a defined public API. Other modules cannot reach into its internals or directly query its tables — they go through the module interface.
You get the operational simplicity of one deployment with the organisational clarity of microservices. Shopify has run this way at significant scale. The other benefit: if you do eventually need to extract a service, a well-bounded module is already halfway there. The hard work of defining boundaries and enforcing them has been done; extraction becomes a packaging exercise rather than a redesign.
The Strangler Fig Pattern
If you're sitting on an existing monolith and need to migrate toward services, don't rewrite. Rewrites are notoriously risky — the new system has to reach feature parity with the old one before you can switch, and features keep being added to the old one during the rewrite.
The strangler fig pattern is how you do it incrementally. Route new features to new services from day one. Gradually migrate existing functionality piece by piece. The old monolith handles less and less over time as the new services handle more and more — until you can finally shut it down. The name comes from a fig tree that grows around a host tree, eventually replacing it.
[API Gateway / Router]
/ \
[Legacy Monolith] [New Services]
(handles old paths) (handles new paths)
Amazon used this approach in 2002 when Jeff Bezos issued his famous mandate: all teams must expose their functionality as service interfaces. All team communication must go through those interfaces. There are no exceptions. That mandate, enforced over years, is how Amazon built AWS.
Key Takeaways
- Start with a monolith — the complexity of microservices is only justified when you have the team size and scale to warrant it
- Microservices introduce real costs: network latency, distributed tracing, eventual consistency, and significant operational overhead
- The organisational argument for microservices (independent team deployments) is often more compelling than the technical one
- The modular monolith is an underrated middle ground — one deployment, clean internal boundaries, extractable if needed
- Never draw microservice boundaries before your domain is well understood; wrong boundaries are expensive to fix
- The strangler fig pattern lets you migrate from monolith to services incrementally without a big-bang rewrite
Next up: consistent hashing — the technique that makes adding or removing servers in a distributed system far less disruptive than you'd expect.
Enjoyed this breakdown?
Get new lessons in your inbox.