Skip to content

Monolith First

Most successful microservice architectures started as monoliths. This is not a coincidence. Microservices solve a particular set of problems that come with scale, organizational complexity, and high operational maturity. Adopting them before those problems actually exist means paying their costs without earning their benefits.

The default for a new system should be a well-structured monolith. Decomposition into services should be a deliberate response to specific problems the monolith cannot solve, not a premature commitment based on what a successful eventual system might look like.12

The Real Cost of Distributed Systems

A microservice architecture trades some kinds of complexity for others. The trades are sometimes worth it. They are never free.

  • Network complexity. Function calls become network calls. Network calls fail, time out, partially complete, and arrive out of order. Every interaction between services has to handle these conditions.
  • Deployment coordination. Changes that span multiple services require coordinated releases, backward-compatible interfaces, and a deployment pipeline that can keep many moving parts consistent.
  • Distributed debugging. A single user request that crosses five services produces failure modes that no single service's logs can explain. Investigating a production incident becomes an exercise in correlating evidence across systems.
  • Data consistency challenges. A single database can provide strong consistency cheaply. Multiple services with their own databases cannot. The team now owns the work of designing eventual consistency, sagas, and reconciliation processes.
  • Operational overhead. Each service has its own deployment pipeline, monitoring, alerting, on-call rotation, dependency tree, and security boundary. The marginal cost of an additional service is much higher than it looks.
  • Observability requirements. Tracing across services requires distributed tracing infrastructure, structured logging, and correlation IDs flowing through every request. The investment is large and front-loaded.

A team that adopts microservices before it has solved monolith-stage problems pays all of these costs immediately. The benefits, meanwhile, take years to manifest, and only if the team grows into them.

Why Monoliths Win at the Start

A well-structured monolith has several advantages that are hard to appreciate until you give them up:

  • One deployment unit, one transaction boundary. Most of the consistency problems a microservice architecture has to solve do not exist in a monolith.
  • Refactoring across the entire codebase is cheap. Renaming a function, restructuring a module, or changing an interface is a single-PR change. In a microservice architecture, the same change can require months of coordination.
  • The seams are not yet known. Most early-stage products do not yet know which parts of the system have which responsibilities. Decomposing prematurely freezes the wrong seams into place. The team will discover the right seams as the product matures; by then, the monolith is easier to decompose along lines that the team actually understands.3
  • One thing to deploy, one thing to debug, one thing to monitor. The team can focus on the product instead of on the operational complexity of distribution.
  • Smaller teams operate it well. A monolith is a system that a small team can hold in its head and maintain effectively. Microservices push toward team-per-service structures that small teams cannot staff.

When to Decompose

Decomposition is sometimes the right answer. The question is when, and the right time is when specific problems make decomposition cheaper than continuing as a monolith:

  • Independent scaling needs. When one part of the system has scaling requirements that are orders of magnitude different from the rest, separating it can be cheaper than over-provisioning the whole monolith to match.
  • Independent deploy cadences. When different parts of the system need to release on different cycles, and the monolith's release cadence is becoming a constraint, the slowest-changing parts can be extracted.
  • Team boundaries. When the team grows past the size where everyone can credibly work in the same codebase, Conway's Law starts to pull the architecture in the direction of organizational structure. Working with it is easier than working against it.
  • Distinct operational profiles. When parts of the system have radically different reliability, security, or compliance requirements, separating them can be cheaper than holding the whole monolith to the highest standard.
  • Well-understood seams. When the team genuinely understands where the natural boundaries in the system are, and those boundaries have been stable for months, decomposition can codify what is already true.

In none of these cases is "we want to be using microservices" the actual driver. The driver is a specific problem the current architecture has.

Common Anti-Patterns

  • Microservices by default. The new system is decomposed into services before the first version ships, because that is the modern way to build. The team pays distributed-system costs to ship a product that could have been a single deployable.
  • Distributed monolith. The system is technically composed of services, but they share a database, deploy together, and cannot be released independently. The team has all the costs of distribution and none of the benefits.
  • Service-per-developer. Every engineer creates a new service for their work, and the architecture ends up with more services than engineers. Operational load grows superlinearly.
  • Decomposition along the wrong seams. Services are split where it was easiest to split them, not where the real responsibilities lie. Every meaningful change requires touching multiple services.
  • No path back. Once the team has decomposed, there is rarely a serious discussion about whether to recompose any part. Distribution becomes a one-way door.

What This Looks Like in Practice

  • Start with a well-structured monolith, with internal modules that respect the boundaries you currently understand. Modular monoliths give you most of the architectural benefits with none of the operational ones.
  • Defer distribution until a specific problem requires it. "We might need to decompose someday" is not a problem; "this part of the system has scaling needs the rest does not share" is.
  • When you do decompose, extract one piece at a time. A staged decomposition lets the team learn whether the seam was actually a seam, and back out cheaply if it was not.
  • Watch for the distributed-monolith trap. If the services have to release together, share a database, or cannot be developed independently, the team is paying for distribution without getting it.

Key principle

Do not pay for complexity before the product has earned it. Simple systems are easier to understand, cheaper to operate, and easier to change. The right time to introduce distributed-system complexity is when concrete problems demand it, not before.

See also: Architecture Is About Change, Maintainability, Trunk-Based Development, Software Is Economics.



  1. Martin Fowler, MonolithFirst (2015). The essay that articulated the now widely-accepted argument that microservice architectures almost always work better when extracted from a working monolith, rather than designed from scratch: https://martinfowler.com/bliki/MonolithFirst.html 

  2. Sam Newman, Building Microservices: Designing Fine-Grained Systems (2nd edition, O'Reilly, 2021). The reference text on microservices. Its most important content is the discussion of when not to use them and the careful catalog of the operational costs they impose. 

  3. Martin Fowler, Yagni ("You Aren't Gonna Need It"). On the cost of building flexibility for needs that never materialize: https://martinfowler.com/bliki/Yagni.html