Architecture

Microservices vs Monolith

Choosing between a single deployable monolith and a fleet of independently deployed services is fundamentally a tradeoff between development simplicity and organizational scalability. Most systems should start as a well-structured monolith and split only when concrete pressure demands it.

The Monolith and the Modular Monolith

A monolith is a single deployable unit: one codebase, one build pipeline, one process (possibly horizontally scaled behind a load balancer). Calls between modules are in-process function calls, which are roughly 1000x faster than network calls (nanoseconds vs milliseconds) and cannot partially fail. Transactions across modules are ordinary database transactions with ACID guarantees. Debugging is a single stack trace, and refactoring across module boundaries is a compiler-checked rename.

The modular monolith is the disciplined version of this: a single deployable with strictly enforced internal boundaries, typically one module per business capability, communicating only through well-defined interfaces and ideally owning separate schemas or schema namespaces. Shopify runs one of the largest Rails applications in the world as a modular monolith with enforced component boundaries, and it handles Black Friday traffic in the millions of requests per minute. The key insight is that most benefits people attribute to microservices (clear ownership, independent modules, well-defined contracts) are achievable inside one deployable if the team has discipline.

The monolith's real failure modes are organizational and operational: build and test times grow superlinearly with codebase size, a single bad deploy takes down everything, all modules must scale together even if only one is hot, and the whole codebase is locked to one language and runtime. When a team of 200 engineers is queueing behind one deploy train, the monolith has become the bottleneck.

What Microservices Actually Buy You

Microservices decompose the system into independently deployable services, each owning its data and communicating over the network via APIs or messages. The primary benefit is independent deployability: team A can ship 20 times a day without coordinating with team B. This maps to Conway's Law, since the architecture mirrors and enables the org structure. Amazon's famous two-pizza teams each own services end to end, and Netflix runs on the order of a thousand microservices, which lets hundreds of teams deploy independently thousands of times per day.

Secondary benefits include independent scaling (scale the video-encoding service to 500 instances while the billing service runs on 3), fault isolation (a memory leak in recommendations does not crash checkout, assuming proper bulkheads), and technology heterogeneity (a JVM service next to a Go service next to a Python ML service). Each service can also choose the datastore that fits its access pattern, such as Postgres for orders and Elasticsearch for search.

None of these benefits are free, and crucially, none of them matter much below a certain team size. A 5-person startup gets zero value from independent deployability because there is only one deploy stream anyway, but pays the full distributed-systems tax.

The Operational Cost of Microservices

Every in-process call that becomes a network call inherits latency, partial failure, retries, and timeouts. A user request that fans out across 10 services must reason about what happens when service 7 times out. You now need distributed tracing to debug anything, service discovery, per-service CI/CD pipelines, contract testing or schema registries to prevent breaking API changes, and an on-call rotation that understands cross-service failure modes. Segment famously published a post-mortem about consolidating hundreds of microservices back toward a monolith because the operational overhead of managing that many repos, queues, and deploy pipelines overwhelmed a small team, and Amazon Prime Video reported a 90 percent cost reduction after merging a distributed pipeline back into a monolithic process.

Data consistency is the deepest cost. Once orders and inventory live in different services with different databases, you lose cross-entity ACID transactions and must adopt sagas, outbox patterns, and eventual consistency, each of which adds code, failure modes, and reconciliation jobs. A common estimate is that a microservices architecture requires a dedicated platform team once you pass roughly 20 to 30 services, which is headcount a monolith does not need.

The distributed monolith is the worst outcome: services that must be deployed together, share a database, or call each other synchronously in long chains. It has the operational cost of microservices and the coupling of a monolith. If two services always change together, they should be one service.

Service Boundaries and When to Split

Good service boundaries follow business capabilities, not technical layers. Split by domain (orders, payments, inventory, identity) using domain-driven design bounded contexts, never by tier (a UI service, a business-logic service, a database service), because layer-based splits mean every feature touches every service. A well-drawn boundary has high cohesion inside, low coupling outside, and can be described in one sentence of business language. Each service must own its data exclusively; shared databases are the number one cause of distributed monoliths.

Signals that it is time to extract a service: deploy contention (multiple teams blocked on one release train), a component with radically different scaling needs (the image-processing path needs GPUs, the rest does not), a component with different availability or compliance requirements (PCI scope isolation for payments), or a subsystem with a genuinely different rate of change. Extract incrementally using the strangler fig pattern: route a slice of traffic to the new service behind the existing interface, verify with shadow traffic or dual writes, then cut over. Rewriting everything at once is how migrations die.

A sensible default answer in interviews: start with a modular monolith, enforce module boundaries and separate schemas from day one, and extract services only when a specific pain (team scaling, independent scaling, isolation) justifies the operational cost. This shows judgment rather than cargo-culting.

Key points

  • Monolith advantages: in-process calls, ACID transactions across modules, single deploy and debug surface, low operational overhead.
  • Microservices advantages: independent deployability per team, independent scaling, fault isolation, per-service technology and datastore choice.
  • The modular monolith captures most modularity benefits (clear boundaries, ownership) without the distributed-systems tax; Shopify runs at massive scale this way.
  • Split along business capabilities (bounded contexts), never technical layers, and give each service exclusive ownership of its data.
  • The distributed monolith (services that share databases or must deploy together) combines the worst of both worlds.
  • Migrate incrementally with the strangler fig pattern; extract the service with the clearest boundary and highest pain first.

Tradeoffs

Monolith (or modular monolith)

Pros

  • + Simple deployment, testing, and local development; one pipeline and one artifact
  • + In-process calls and ACID transactions; no partial failure between modules
  • + Far lower infrastructure and platform-team cost; ideal below roughly 50 engineers

Cons

  • Whole system scales, deploys, and fails as one unit
  • Build and test times grow with codebase; deploy trains create team contention
  • Locked to one language and runtime; boundaries erode without discipline

Microservices

Pros

  • + Independent deploys enable many teams to ship in parallel (Netflix, Amazon scale)
  • + Independent scaling and fault isolation per service
  • + Freedom to pick the right language and datastore per service

Cons

  • Network calls introduce latency, timeouts, retries, and partial failure everywhere
  • No cross-service ACID; requires sagas, outbox, and eventual consistency
  • Heavy operational burden: tracing, service discovery, contract testing, platform team

In the interview

  • Default to 'start with a modular monolith, split when specific pressure appears' and name the pressures: deploy contention, divergent scaling needs, compliance isolation.
  • Explicitly mention Conway's Law: microservices are as much an organizational tool as a technical one, so team size drives the decision.
  • Call out the distributed monolith anti-pattern and the shared-database anti-pattern; interviewers listen for these.
  • If asked to split a monolith, describe the strangler fig migration concretely: pick one bounded context, put a facade in front, dual-run, cut over.

Related topics