Vasu Patel logoVasu Patel
← Designing at Scale: System Design
All chapters in this track
  1. 01Chapter 1: Why System Design Is Its Own Skill
  2. 02Chapter 2: Core Building Blocks and Vocabulary
  3. 03Chapter 3: Eliciting Non-Functional Requirements
  4. 04Chapter 4: Back-of-Envelope Capacity Estimation
  5. 05Chapter 5: Caching Strategies and Cache Invalidation at the Systems Level
  6. 06Chapter 6: Load Balancing and Horizontal Scaling
  7. 07Chapter 7: Database Selection, Replication, and Sharding at a Systems Level
  8. 08Chapter 8: Asynchronous Processing, Message Queues, and Event-Driven Design
  9. 09Chapter 9: API Design from a Systems Perspective: REST, gRPC, GraphQL
  10. 10Chapter 10: The CAP Theorem and Consistency Models in Practical Terms
  11. 11Chapter 11: Microservices vs. Monoliths and Drawing Service Boundaries
  12. 12Chapter 12: Designing for Failure: Redundancy, Failover, Graceful Degradation
  13. 13Chapter 13: A Repeatable Framework for Any System Design Problem
  14. 14Chapter 14: Worked Example: URL Shortener
  15. 15Chapter 15: Worked Example: Chat System
  16. 16Chapter 16: Worked Example: News Feed
  17. 17Chapter 17: Worked Example: Rate Limiter

Chapter 11 of 17

Chapter 11: Microservices vs. Monoliths and Drawing Service Boundaries

Why this matters

"Should this be microservices?" is one of the most consequential, and most frequently mishandled, questions in system design — mishandled specifically because microservices carry real, substantial operational costs that are easy to underestimate when focused only on their commonly-cited benefits, and because a poorly-drawn service boundary can make a microservices architecture worse than the monolith it replaced.

The monolith: one deployable unit, simpler by default

[BEGINNER] A monolith is a single, deployable application containing all (or most) of a system's business logic — one codebase, one deployment, typically one database. Its genuine strengths: simpler to develop (no network calls between components, easier local debugging), simpler to deploy (one thing to deploy and monitor), and simpler to maintain data consistency within (a single database can use real transactions, companion Database book Chapter 3, across what would otherwise be multiple services' data).

Microservices: independent services, genuine benefits and genuine costs

[BEGINNER] Microservices split a system into multiple independently-deployable services, each typically owning its own data and communicating over the network (Chapter 9). Genuine benefits: independent deployment (one team can ship changes to their service without coordinating a release with every other team), independent scaling (scale only the specific service under heavy load, not the entire system), and technology flexibility (different services can use different languages/databases suited to their specific needs).

[SENIOR INSIGHT] The genuine costs, which are just as real and frequently under-weighted in casual discussion: network calls replace function calls, adding real latency and a new category of failure mode (companion Networking book's Chapters 9 and 11 — timeouts, retries, cascading failures) that simply doesn't exist within a single process; distributed transactions become genuinely hard (a monolith's single-database transaction spanning multiple entities becomes, across services, a much harder problem requiring patterns like sagas — a company Database book, Chapter 3 concern, made significantly more complex once the data lives in separate databases owned by separate services); and operational complexity multiplies (more services to deploy, monitor, and keep healthy, each potentially with its own on-call burden, per the companion SDLC book's Chapter 10).

The decision: driven by organizational scale, not by trend

[IN PRACTICE] The single most reliable, evidence-based heuristic for this decision: microservices solve an organizational scaling problem (many teams needing to deploy independently without coordinating with each other constantly) more than a technical one. A small team (the companion SDLC book's Chapter 12 five-person startup) gets essentially none of microservices' organizational-independence benefit (there's only one team; there's nothing to decouple), while paying its full network/operational cost immediately — this is exactly why a monolith is usually the right default for a small team, and "we should use microservices because that's what scalable companies do" is a common, costly example of cargo-cult reasoning disconnected from the actual problem microservices solve.

Weak reasoning: "Big tech companies use microservices, so we should too,
                for scalability."

Strong reasoning: "We're a 6-person team building one product -- there's
                   no organizational coordination problem microservices
                   would solve for us right now, and we'd pay real
                   network/operational complexity costs for a benefit
                   we can't actually realize at this team size. A
                   monolith is the right default; we'd revisit this if
                   we grow to multiple independent teams needing to ship
                   on separate schedules."

Drawing service boundaries well, when microservices genuinely are warranted

[SENIOR INSIGHT] Once microservices are genuinely justified (real organizational scale, or a genuine, specific technical need like independent scaling for one particularly heavy sub-system), the boundary-drawing question is exactly the companion OOP book's Chapter 9 Single Responsibility Principle, applied at the service-architecture scale rather than the class scale: a service boundary should align with a genuine, cohesive business capability ("orders," "inventory," "user accounts") — not an arbitrary technical split (a "database service" and a "logic service," which would just relocate a monolith's internal coupling into constant, chatty network calls between two services that always need to change together anyway). A well-drawn boundary means most changes stay within one service; a poorly-drawn boundary means most changes require coordinated updates across multiple services — the worst of both monolith and microservices worlds simultaneously (network complexity without genuine independence).

Poor boundary (technical split, not business-capability-aligned):
"OrderDatabaseService" and "OrderLogicService" -- these almost ALWAYS
change together, so splitting them just adds a network call between
two things that have no real independence from each other.

Good boundary (business-capability-aligned):
"OrderService" (owns everything about placing/tracking orders) and
"InventoryService" (owns everything about stock levels) -- these
genuinely CAN evolve somewhat independently, and the boundary matches
a real, cohesive business concept each team can reason about as a whole.

Common mistakes

  1. Adopting microservices because it's the current industry trend, without connecting the decision to an actual organizational scaling need or a specific, genuine technical requirement this particular system has.
  2. Underestimating the real network/operational costs of microservices (distributed transactions, cascading failure risk, multiplied on-call burden) relative to their commonly-cited benefits.
  3. Drawing service boundaries around technical layers rather than business capabilities, producing services that always change together — paying network complexity cost without gaining genuine independence.
  4. Applying a monolith's internal design mistakes (poor internal module boundaries) as an excuse to jump to microservices, when the actual fix (per the companion Coding Standards book's Chapter 6) is better internal module organization within the existing monolith — microservices don't automatically fix poor boundaries; they just make poor boundaries far more expensive to work around.

Discussion / interview questions

  1. "When would you choose microservices over a monolith for a new system?" Strong answer: When there's a genuine organizational scaling need (multiple teams needing to deploy independently without constant cross-team coordination) or a specific technical requirement (one sub-system needing very different, independent scaling) — not by default, and not because it's a current industry trend.

  2. "What are the real costs of a microservices architecture that are easy to underestimate?" Strong answer: Network calls replacing function calls (added latency, new failure modes like timeouts and cascading failures), distributed transactions becoming genuinely hard (requiring patterns like sagas instead of a simple database transaction), and multiplied operational complexity (more services to deploy, monitor, and staff on-call for).

  3. "How would you draw service boundaries in a microservices architecture, and what makes a boundary 'good' versus 'poor'?" Strong answer: Align boundaries with genuine, cohesive business capabilities (orders, inventory, user accounts), not arbitrary technical splits (a database service and a logic service) — a good boundary means most changes stay within one service; a poor boundary means changes routinely require coordinated updates across multiple services, the worst of both architectures at once.

  4. "A small, 6-person team is considering adopting microservices 'to scale like a big tech company.' What would you say?" Strong answer: Question the actual problem microservices would solve for this specific team — microservices primarily solve an organizational coordination problem that doesn't exist at this size (one team, nothing to decouple), while imposing real network and operational costs immediately; a monolith is the more evidence-based default here, revisited if and when genuine organizational scale develops.

Chapter summary / cheat sheet

  • Monolith: simpler development, deployment, and data consistency (real transactions within one database) — the right default absent a specific reason for microservices.
  • Microservices: independent deployment/scaling and technology flexibility, at the real cost of network-call latency/failure modes, hard distributed transactions, and multiplied operational complexity.
  • The decision should be driven by genuine organizational scaling needs (many teams needing independent deployment) or a specific technical requirement — not by industry trend.
  • Draw service boundaries around cohesive business capabilities, not technical layers — a poorly-drawn boundary produces services that always change together, paying microservices' costs without gaining its actual benefits.