Archtin
All articles
ArchitectureSystem Design10 min read

Layered (N-Tier) Architecture: The Industry Default, Done Properly

Presentation, business, persistence, database — the layered style is the most used and least examined architecture in software. Here is how it really works, when it helps, and the sinkhole and leaky-layer traps that ruin it.

The structure

Layered architecture partitions a system horizontally by technical responsibility. Presentation handles input and output, business holds the rules, persistence translates between objects and storage, and the database stores state. Each layer is a logical grouping — they usually all run in the same process.

Presentationcontrollers, views, API serialisersBusinessuse cases, domain rules, validationPersistencerepositories, queries, mappingDatabasetables, indexes, constraintsclosed layers:no skipping
The canonical four-layer stack. Each layer depends only on the one beneath.

Closed layers and layer isolation

The property that makes layering worth anything is layer isolation: a change in one layer must not force changes in others. That requires layers to be closed — a request must pass through every layer, and presentation may never call persistence directly.

This feels wasteful, and that is the trade. Closed layers mean you can replace the whole persistence layer without touching business logic, and swap REST for gRPC without touching anything below presentation. Open a layer "just for performance" and you have traded the only benefit you were paying for.

When to open a layer deliberately
A common, defensible exception is a read-only "shared services" layer that business and presentation may both use (formatting, feature flags, i18n). Make it explicit and dependency-free — not a dumping ground.

A request through the layers

POST /orders
  presentation:  OrderController.create(req)
                 -> validate shape, authn/z, map JSON -> CreateOrderCommand
  business:      PlaceOrder.execute(command)
                 -> check stock rules, pricing, credit limit
                 -> build Order aggregate
  persistence:   OrderRepository.save(order)
                 -> map aggregate -> rows, one transaction
  database:      INSERT INTO orders ...; INSERT INTO order_lines ...

response bubbles back up, mapped to an OrderResponse DTO
Each layer does one job and hands down a type the next layer understands.

Note the mapping at each boundary. Passing the database row all the way up to the HTTP response is the single most common way layered systems become impossible to change: the table schema becomes the public API.

What it's genuinely good at

  • Onboarding. Any engineer can find anything in minutes. That is a real, underrated architectural quality.
  • Separation of technical concerns. Persistence changes (new ORM, new database) stay contained.
  • Testability by layer. Business logic can be tested with fake repositories.
  • Cost. Zero infrastructure, zero distribution, works inside a monolith or inside a single microservice.

The sinkhole anti-pattern

A sinkhole request is one that passes through every layer while each layer does nothing but forward it — typically simple reads. If 80% of your requests are sinkholes, the layering is pure overhead.

The fix is not to abolish layering; it is to allow a documented read path that bypasses the business layer for pure queries (effectively CQRS-lite), while all writes stay closed. Aim for roughly 20% sinkholes; beyond that, question the style.

Leaky layers

LeakSymptomFix
ORM entities in controllersSchema change breaks the API contractDTOs at the presentation boundary
SQL in business logicCannot change storage without touching rulesRepository interfaces
HTTP types in business layerDomain code imports the web frameworkCommands and plain results
Business rules in the databaseLogic split across triggers and codeKeep invariants in one place
Persistence calling businessCycles, unclear ownershipEvents or an application service above both

Layered vs hexagonal vs vertical slices

Layering partitions by technology. Its structural weakness is that a single feature cuts across all layers, so feature work is never local.

  • Hexagonal keeps a similar dependency discipline but inverts it: the domain defines the interfaces and infrastructure implements them, so dependencies point inward rather than downward.
  • Vertical slice organises by feature first (orders/place-order/ containing its handler, rules and query), keeping change local at the cost of some repetition.

These combine well: vertical slices on the outside, layer discipline inside a slice, hexagonal ports where infrastructure is touched.

Rules that keep it healthy

  1. Layers are closed. Skipping is a reviewed exception, not a habit.
  2. Every boundary maps types. No entity escapes persistence; no DTO enters the domain.
  3. Dependencies point one direction only — enforce it with an import linter.
  4. Business logic never imports the web or ORM framework.
  5. Measure your sinkhole ratio; a high one means the style is mismatched to your workload.

Interview framing

Strong answer

"Layered inside each service, closed layers, DTO mapping at both boundaries. It gives us the fastest onboarding and contains storage changes. Its weakness is that a feature touches four layers, so for the read-heavy dashboard I'd add a dedicated query path that skips the business layer and hits read models directly, while all writes remain fully layered."

Traps

  • Calling layered architecture "n-tier" and implying physical separation — tiers are deployment, layers are logical.
  • Claiming layering gives domain modularity. It gives technical modularity only.

Keep reading

Suggested next articles based on this one.

Design it, don't just read it.

Practise LLD and system design problems with structured rubrics and AI feedback.

Start practising free