Microservices vs Monolith

The real costs of splitting a system, and the modular monolith as a middle path.

9 min read · lesson 29 of 46

Microservices are often presented as the grown-up architecture and monoliths as legacy, but each is a trade-off between team independence and operational complexity. Senior engineers are expected to argue for the boundary that fits the team and the problem, not for a fashion.

The analogy

A monolith is a single large restaurant kitchen: everyone shares the same space, ingredients, and head chef, so coordination is easy, but one fire closes the whole kitchen and a new menu means everyone changes together. Microservices are a food court: each stall runs independently with its own staff and hours, but getting a combined meal requires walking between stalls, and the food court needs shared plumbing, security, and cleaning.

What each gives you

MonolithMicroservices
DeploymentOne artifact, one pipelineIndependent per service
ScalingScale the whole appScale hot services independently
DataOne database, easy joins and transactionsDatabase per service; consistency via events and sagas
CallsIn-process function calls (nanoseconds)Network calls (milliseconds), can fail
Team autonomyShared codebase, coordination neededTeams own services end to end
TechnologyOne stackPolyglot possible
DebuggingOne stack traceDistributed tracing required
Failure isolationOne bug can crash everythingFailures can be contained (with effort)
Operational overheadLowHigh: service discovery, observability, CI/CD per service, versioning

The modular monolith

A middle path: one deployable, but code organized into strict modules with clear interfaces and separate schemas or tables per module. You get easy transactions and simple operations now, and clean seams for extracting services later. Many successful companies run large modular monoliths for years.

Modular monolith                         Microservices+------------------------------+         +--------+  +--------+  +---------+| [Users] [Orders] [Payments]  |         | Users  |  | Orders |  | Payment ||   |        |         |       |   -->   |  DB    |  |  DB    |  |   DB    ||  schema   schema   schema    |         +---+----+  +---+----+  +----+----+|      one database            |             \___ events / APIs ___/+------------------------------+

When microservices make sense

  • Many teams (dozens of engineers) block each other in a shared codebase and release train.
  • Parts of the system have very different scaling, latency, or reliability profiles (a video transcoder versus a profile service).
  • Different compliance boundaries (PCI scope for payments).
  • The domain boundaries are well understood and stable.

When they do not

  • Small team, early product, boundaries still changing.
  • Most features touch most services, so every change needs coordinated deploys (a "distributed monolith").
  • No investment yet in automation, observability, and on-call maturity.

Drawing boundaries

Use domain-driven design ideas: split along bounded contexts where the language and data ownership change (catalogue, ordering, payments, shipping). Each service owns its data; other services access it only through APIs or events. Boundaries that require frequent synchronous calls or shared tables are wrong.

Communication

  • Synchronous (REST/gRPC) for queries that need an immediate answer; watch for long call chains that multiply latency and failure probability.
  • Asynchronous events for state changes other services react to; improves decoupling and resilience.
  • Prefer choreography via events for simple flows and orchestration (a saga coordinator) for complex multi-step workflows.

Common mistakes

  • Splitting by technical layer (a "database service", a "validation service") instead of by business capability.
  • Sharing one database across services, which couples them at the schema level.
  • Chains of five synchronous hops on the critical path.
  • Migrating to microservices to fix problems that are really about code quality or testing.

In the interview

Q: Would you start this product as microservices? Usually a modular monolith first, with clear module boundaries and separate schemas, extracting services when team size or scaling needs justify it. In a system-design interview you may still draw separate services to show logical boundaries; say that they could be deployed together initially.

Q: How do you handle a transaction that spans the order and payment services? Avoid distributed ACID transactions; use a saga with compensating actions, and publish events reliably with a transactional outbox.

Q: What is a distributed monolith? Services that must be deployed together and call each other synchronously for every operation, so you get the costs of microservices without the independence.

Key takeaways

  • Microservices buy team autonomy and independent scaling at a large operational cost.
  • A modular monolith is often the right starting point.
  • Split by business capability and data ownership, never share databases.
  • Favour asynchronous events between services to limit coupling.
Complete & continue