Level 1 · 20 min
Monolith vs Microservices
The monolith vs microservices decision is the most consequential architectural choice for a system's long-term evolution. Neither is universally correct — the right answer depends on team size, domain complexity, and operational maturity.
Monolith Trade-offs
A monolith deploys all components together as a single process. Advantages: simple deployment (one artifact), easy debugging (single process, single log stream), no distributed systems complexity, ACID transactions across the entire domain, low operational overhead. Disadvantages: single deployment means all components scale together (memory-hungry analytics can't be scaled independently from the API), a single codebase becomes harder to navigate as it grows, a single team bottleneck (multiple teams must coordinate code changes), a single language/runtime for the entire application. The modular monolith is a middle ground: a monolith internally organized into well-defined modules with explicit interfaces, deployable as one unit but maintainable like multiple services.
Microservices Trade-offs
Microservices split the system into independently deployable services. Advantages: each service scales independently, teams own their service end-to-end (Conway's Law: organizations build systems that mirror their communication structure), technology heterogeneity (Java for compute-intensive services, Go for high-throughput, Python for ML). Disadvantages: distributed systems are fundamentally harder — network failures, partial failures, eventual consistency. Each service needs its own deployment pipeline, monitoring, logging, and health checks. Cross-service transactions require sagas. Debugging distributed traces requires APM tooling. The microservices premium (Martin Fowler) is real: the operational overhead is only justified when the benefits (independent scaling, team autonomy) outweigh the costs. Key trade-off from practice: Newman notes that 'independent deployability is key' — a service that cannot be deployed without coordinating with other teams delivers none of the autonomy benefits. Shopify ran a large-scale e-commerce platform as a modular monolith for years, demonstrating that a modular monolith — where the database is also decomposed along module lines — can achieve near-microservice team independence without the distributed systems tax. The critical insight: 'the database tends to lack the decomposition we find at the code level, leading to significant challenges if you want to pull apart the monolith in the future.' — Sam Newman, Building Microservices (2nd ed.)
When to Split and Strangler Fig
Extract to microservices when: a specific domain subdomain has different scaling requirements, a team's deployment cycle is bottlenecked by other teams' code, a component needs a different technology stack, compliance requires data isolation. The Strangler Fig pattern (Martin Fowler): incrementally migrate a monolith to microservices by placing a facade (proxy) in front of the monolith. New features and refactored domains are implemented as standalone services behind the facade. Gradually, the monolith is 'strangled' as more functionality moves to services. The facade eventually routes all traffic to services and the monolith is retired. This avoids the 'big rewrite' anti-pattern.
Code example
// Strangler Fig: facade routes to old monolith or new service\n// Phase 1: all requests to monolith\nGET /api/orders → monolith.orders\n\n// Phase 2: orders service extracted\nGET /api/orders → orders-service (new)\nGET /api/users → monolith.users (still in monolith)\n\n// Phase 3: users extracted, monolith retired\nGET /api/users → users-service (new)