Module F-7·16 min read

Why the monolith is almost always the right first answer, what a tier really buys you, and how layer boundaries decay when nobody defends them.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module F-7 — Client–Server, N-Tier, and the Monolith

What this module covers: The shape of the application behind the wire. This module covers the client–server model and where the trust boundary actually sits, what a tier buys you that a layer doesn't, how to structure a monolith so its layers survive contact with deadlines, and the modular monolith as the design that gets you most of what people want from microservices without a distributed system. It makes the case that the monolith is almost always the correct first answer — and then names its costs honestly, along with the specific signals that mean it's time to split.


Layers and Tiers Are Different Things

These get used interchangeably and shouldn't be.

A layer is a logical separation inside one codebase: HTTP handlers call services, services call repositories, repositories talk to the database. It's an organisational boundary enforced by discipline and tooling.

A tier is a physical separation: a separate process, on a separate machine, reachable over a network. It's enforced by the network itself.

The classic three-tier architecture has a presentation tier (web server), an application tier (business logic), and a data tier (database) as three deployable units. A well-structured monolith has the same three layers in one deployable unit.

The distinction matters because a tier boundary is a network call, and a network call is a failure mode. Converting a layer into a tier buys you independent scaling, an independent security boundary, and independent technology choice. It costs you serialisation, network latency, partial failure, and a distributed debugging problem. Every module in Phase 3 of this course is, in some sense, the bill for that conversion.

Analogy: layers are rooms in a house; tiers are separate buildings. Rooms give you organisation for free — a wall is cheap, and walking between rooms costs nothing. Separate buildings give you real isolation (one can burn down without the other) but now everything is a trip outside, in the weather, and sometimes the road is closed.


Client–Server, and Where Trust Actually Ends

The model is one line: a client sends a request, a server processes it and responds. The client holds no authority.

That last part is the whole of it, and it's the part that gets violated. The trust boundary is the server, and every byte that crosses it from a client is hostile until validated. Consequences that are still routinely got wrong:

  • Validation on the client is a UX feature, not a security control. Anything enforced only in the browser or app is unenforced. curl exists.
  • Prices, totals, discounts, and permissions must be computed server-side. A client that sends { productId, price: 1 } and gets charged $1 is the oldest bug in e-commerce, and it still ships.
  • IDs from a client are a claim, not a fact. GET /api/orders/8241 must verify that the authenticated user owns order 8241. Skipping that is IDOR — a broken-access-control vulnerability, and consistently one of the most common serious findings in real applications (Module A-13).

Why this matters in production: every one of these is invisible in testing, because your test client is well-behaved. They surface as a security report, not a bug report — and the fix is architectural (validate at the boundary, always, in one place) rather than a patch at each call site.


Why the Monolith Is the Right First Answer

A monolith is one deployable unit containing all the application's logic. Not one file, not one giant function — one deployable.

The argument for starting here isn't nostalgia; it's that a monolith gives you several things for free that a distributed system makes into projects:

  • Transactions actually work. Updating an order and decrementing inventory in one ACID transaction is a BEGIN/COMMIT. Split those across two services and you need sagas, compensation, and an outbox — Module A-3, and it is genuinely hard.
  • Refactoring is safe. Rename a function and the compiler finds every caller. Rename a field in an API contract between two services and you find the callers in production.
  • One place to look. One log stream, one stack trace, one deploy, one version. Distributed tracing (Module O-1) exists to recover in a distributed system what a monolith gives you by default.
  • No network between your own components. No serialisation cost, no partial failure, no retry logic, no circuit breaker, no timeout tuning between your own modules.
  • You don't know the boundaries yet. This is the strongest argument. Service boundaries encode assumptions about which things change together, and early on you're wrong about that. A wrong boundary inside a monolith is a refactor; a wrong boundary between services is a migration with a coordination cost across teams.

And the honest costs, which are real:

  • One deploy for everything. A risky change to reporting ships alongside checkout. Mitigated substantially by feature flags and canary deploys (Module O-4), not eliminated.
  • Scaling is all-or-nothing. If image processing needs CPU, you scale the whole application to get it. Wasteful, though often cheaper than the alternative until the waste is large.
  • One runtime, one language. No dropping into Rust for the hot path or Python for the ML piece without a separate process.
  • Blast radius. A memory leak in a rarely-used admin endpoint can take down checkout, because it's the same process.
  • Coordination cost grows with team size. At some headcount, everyone editing one codebase and one deploy pipeline becomes the bottleneck. This is a genuine limit, and it's about organisation, not technology.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.