Free sample lesson ยท Architecture Patterns and Trade-Offs

Monoliths

Why do fast-moving engineering teams still choose single-codebase architectures?

Lesson 1 of the Wondering course Architecture Patterns and Trade-Offs

One Artifact, One Deploy

A monolith organizes an application as one deployable system: one build artifact, one release, one runtime process.

Inside that process, modules call each other directly in memory. There is no network hop and no partial failure between them.

Nested scope of one monolith
  • Deployable artifact: One build artifact ships the whole application.
  • Runtime process: One release runs as a single process.
  • Modules: Modules live inside that same process.
  • In-process calls: Modules call each other directly in memory.

Each layer contains the next, with no network hop between them.

That is a choice about deployment boundaries, not a verdict on code quality.

Fast teams keep choosing it because this boundary is cheap to operate.

Modules Still Have Boundaries

A monolith is not required to be unstructured. Modules such as orders, billing, and notifications can each own their logic and interfaces.

Inside a modular monolith
  • Single deployable: Ships as one unit.
  • Orders module: Owns its own logic and interface.
  • Billing module: Owns its own logic and interface.
  • Notifications module: Owns its own logic and interface.

The difference is enforcement. Those boundaries hold through build rules and review conventions rather than network contracts.

A modular monolith keeps internal seams sharp while shipping as a single unit.

Sharp seams are what make later extraction possible.

Where Simplicity Pays

One repository, one pipeline, one deploy.

A refactor across modules stays atomic, and a single database transaction can span several of them.

Debugging is usually a stack trace instead of a correlated trace across services.

Distributed services trade that away for network contracts, distributed tracing, and partial failure handling.

Monolith or distributed services
  • Monolith
  • Distributed services

Simplicity here is measured in coordination avoided, not lines of code.

Coupling Is The Real Cost

Coupling in a monolith shows up as shared state and shared release cadence.

A shared database schema lets any module read another module's tables, so refactors leak across ownership lines.

One broken test in the shared pipeline blocks every pending change.

A single release train ties all teams to the same cadence, whatever their readiness.

How coupling surfaces
  • Shared schema: Any module can read another module's tables.
  • Ownership lines: Refactors cross them without asking.
  • Broken test: One failure lands in the shared pipeline.
  • Pending changes: All of them wait together.
  • Release train: A single cadence for the whole system.
  • All teams: Readiness differs, cadence does not.

Both costs grow with headcount rather than with code size.

One Process, Shared Fate

Operational boundaries collapse into one process. Modules share the same heap, thread pool, and CPU.

A memory leak in a background report job degrades unrelated request handlers on the same instance.

One swollen module starving its neighbours inside a single process

Notice that the report job and both request handlers draw from the same heap and thread pool, so one module's growth leaves the others with slivers.

Isolation is not free here. It must be bought explicitly, for example by running replicas dedicated to one workload.

Shared fate is the defining operational property of a monolith.

Scaling The Whole Unit

Horizontal scaling in a monolith copies everything. A CPU-heavy report generator forces you to replicate the checkout path with it.

The reverse also holds: a checkout traffic spike replicates idle report capacity.

What a replica actually buys

Often that waste is acceptable, since replicas cost less than the coordination of splitting.

It turns painful when one workload's resource profile diverges sharply from the rest.

When To Change Structure

Structural change earns its cost only when a specific boundary problem is measurable.

Vague dissatisfaction is not a boundary problem.

Ask three questions: are deploys blocked by other teams, does one workload need independent scaling, and is failure isolation required?

Split or stay
  • Boundary problem: A measurable pain, not vague dissatisfaction.
  • Deploys blocked?: Are other teams blocking your releases?
  • Independent scaling?: Does one workload need its own capacity?
  • Isolation required?: Must failure stop at this boundary?
  • Extract one seam: Cut a single boundary, not the whole system.
  • Keep monolith: The cheaper option when every answer is no.
  • Yes
  • No
  • Yes
  • No
  • Yes
  • No
  • Re-evaluate

If every answer is no, the monolith stays the cheaper option.

If one is yes, extract that single seam and re-evaluate.

Extract One Seam At A Time

Migration starts with the seam, not the infrastructure.

Give the module an explicit interface, stop cross-module table access, then move it behind a network call.

Extracting one seam
  • Define interface: Give the module an explicit interface inside the monolith.
  • Cut data access: Stop cross-module table access through the shared schema.
  • Move behind call: Deploy the module separately behind a network call.
  • Measure cost: Check latency, failure modes, and deploy effort before continuing.

Measure latency, failure modes, and deploy effort after each step so the cost of distribution stays visible.

A half-split system is harder to operate than either endpoint, so finish the seam you start.

The Monolith As Benchmark

Treat the monolith as the baseline you must beat.

It fixes the cost of a deploy, a call, and a transaction, which gives you a reference point for every proposed split.

Every distributed boundary should pay for itself in team autonomy, independent scaling, or failure isolation.

If it cannot, the coupling you dislike is simply moving into contracts and networks.

What would splitting actually buy?

Check yourself

Does a monolith normally use one build artifact, one release, and one runtime process?

  • Yes
  • No

Answer: Yes

Yes. A monolith is organized as one deployable system. Its modules may be logically separate, but they ship and run together.

In a monolith, modules call each other _____, share one _____, and run in one _____.

Answer: In a monolith, modules call each other directly in memory, share one release cadence, and run in one process.

Modules communicate without a network hop, teams coordinate around one release schedule, and the modules share one runtime process. These properties explain both the operational simplicity and the shared coupling of a monolith.

A CPU-heavy report generator shares a monolithic application with checkout. What happens when the report workload needs more capacity?

  • Only the report generator is replicated
  • The entire monolith, including checkout, is replicated
  • The report generator is moved automatically to another process
  • The checkout path is removed from each new replica

Answer: The entire monolith, including checkout, is replicated

Horizontal scaling copies the whole monolith. Increasing report capacity also creates more checkout capacity, even if checkout traffic has not increased. This waste may be acceptable when replicas cost less than splitting the system.

Keep going

This is lesson 1 of 7 in Architecture Patterns and Trade-Offs. Next up:

  1. Microservices
  2. Event Driven
  3. CQRS
  4. Sagas
  5. Consistency

Start this course free

Wondering turns any topic into short lessons you can read, watch, or listen to, with practice questions and an AI tutor.