Layered (N-Tier) Architecture

Presentation, application, domain and data access stacked so each layer only calls the one below. Know layers from tiers, strict from relaxed, the sinkhole, and why the arrow from domain to database is the weak spot.

Overview

Layered architecture splits an application by kind of work: code that talks to the user, code that runs a use case, code that holds the business rules, and code that reads and writes storage. Each kind lives in its own layer, and a layer may call the layers below it but never the ones above it. Martin Fowler calls the three-part version (presentation, domain, data) one of the most common ways to modularize an information-rich program.

It is the default most teams reach for without naming it. Most web frameworks ship with folders for views, models and data, and many tutorials teach a controller, a service and a repository. Interviewers ask about it to see whether you can say what belongs in each layer, whether you know a layer from a tier, and whether you can spot the one dependency in the classic stack that points the wrong way.

The four layers

The three-layer split is the classic one. Many codebases add a fourth, thin application (or service) layer between presentation and domain to run one use case from start to finish.

  • Presentation — Parsing input, formatting output, status codes, views — Business rules, SQL — POST /withdraw handler
  • Application (service) — The steps of one use case, the transaction boundary, authorization checks — Rules about money or stock, HTML — WithdrawService.withdraw()
  • Domain — Entities, invariants, calculations, decisions — HTTP, SQL, framework types — Account.withdraw(amount)
  • Data access — Queries, mapping rows to objects, connections — Decisions about the business — AccountStore.load(id)

Fowler's catalog in Patterns of Enterprise Application Architecture describes the Service Layer as the application's boundary: a set of operations that coordinates the response to each one. The Service Layer topic covers it in depth; here it is the layer that turns one request into calls on the domain and the store.

Strict and relaxed layering

In strict (closed) layering a layer may call only the layer directly below it. In relaxed (open) layering a layer may call any layer below it. Microsoft's architecture guide uses the words closed and open for the same two choices.

  • Who may a layer call — Only the next one down — Any layer below
  • Change in data access affects — The layer directly above — Every layer that skipped to it
  • Cost — Pass-through code when a layer has nothing to add — More coupling, harder changes
  • Use it when — The layer in the middle has real rules to enforce — A read path has nothing to decide, such as a report

Microsoft notes that closed layers limit dependencies but may add needless traffic when a layer only passes requests along, and that letting requests skip tiers adds coupling that makes change harder. A common middle ground is strict by default, with a written list of layers that are allowed to be skipped.

Layers are not tiers

A layer is a logical grouping of code. A tier is a physical place where code runs: a machine, a container, a process. Several layers can share one tier, and the number of layers says nothing about the number of tiers.

  • What it is — A group of code with one responsibility — A process or machine that runs code
  • Boundary — A module or package import — A network hop
  • Cost of a crossing — A function call — Latency, serialization, partial failure
  • Why you add one — Separate concerns, ease change — Scale, secure or deploy a part on its own

The classic three-tier web application has a presentation tier, a middle (business) tier and a database tier. Microsoft's guide says physical tiers improve scalability and resiliency but add latency from the extra network calls, and that each tier can sit in its own subnet so the data tier accepts traffic only from the middle tier.

The sinkhole anti-pattern

A sinkhole is a request that passes through every layer while no layer does any work: the controller calls the service, which calls the repository, which runs one query, and each hop only forwards the arguments. Mark Richards describes it in his 2015 O'Reilly report Software Architecture Patterns, and notes that every layered system has some requests like this. A few are fine; when most requests look like this, the layers are cost without benefit.

  • Accept it — A small share of read paths in a system whose writes carry real rules
  • Open that layer for reads — Reports and lookups with nothing to decide; write the exception down
  • Merge the layers — The whole service is a thin form over a table: fewer layers, less code
  • Pick another style — The domain is rich and the store is one of several: see the Hexagonal Architecture topic

Microsoft lists the same smell as a challenge of N-tier systems: a middle tier that only does create, read, update and delete adds latency and complexity without adding value.

Which way the dependencies point

In the classic stack, presentation depends on the domain and the domain depends on data access. Fowler describes this top-to-bottom direction and notes the common variation: put a mapper between domain and data so the domain no longer depends on its data source.

The weak spot is the last arrow. If the domain imports the data access layer, the business rules cannot be compiled, tested or reused without the database code, and a change of storage reaches into the rules. The Dependency Inversion Principle (Robert C. Martin's D in SOLID) fixes it: the domain declares the interface it needs, and data access implements it.

  • Classic layered — Domain depends on data access
  • Layered with DIP — Domain owns the storage interface; data access implements it
  • Hexagonal (Ports and Adapters) — Every outside thing, UI and storage alike, is an adapter behind a port the core owns
  • Clean Architecture — Concentric rings; source code dependencies point only inward

Hexagonal and Clean keep the separation of layers and change one thing: dependencies point toward the rules instead of down toward the database. The Hexagonal Architecture and Clean Architecture topics cover them; the Repository Pattern topic covers the storage interface itself.

A request through three layers

The example has a presentation function, a service, a domain entity and a store. The store is an in-memory dict so the file runs alone. Each assert checks what one layer is responsible for.

The rule about insufficient funds lives in Account, so a batch job or a test can use it without the handler. The handler is the only place that knows about status codes, and the store is the only place that knows the row shape. The failed withdrawal leaves the stored balance alone because the service saves only after the domain accepts the change.

When it is the right default

  • Rules — Moderate; mostly validation and a few calculations — Rich, long-lived rules that must be tested alone
  • Outside systems — One database, one kind of client — Many stores, queues and drivers
  • Team — New to the codebase, needs a familiar map — Comfortable with ports, adapters and wiring
  • Deployment — One deployable unit, scaled as a whole — Parts that must scale or ship on their own
  • Requirements — Still forming — Settled boundaries between business areas

Microsoft's guide lists low learning cost and a natural path from traditional applications among its benefits, and names a monolithic design that blocks independent deployment of features among its costs. Fowler adds a point about size: presentation-domain-data layering works best at a small scale. Once a layer grows large, he suggests splitting the top level by business area first and layering inside each module.

What goes in which layer

A strong answer sorts each concern by asking one question: would this code change if the user interface changed, if the business changed, or if the storage changed? The answer is its layer.

  • Parse JSON, check a field is present — Presentation — It changes when the request format changes
  • Turn an error into a 404 — Presentation — Status codes are part of the protocol
  • Start and commit a transaction — Application — One use case is one unit of work
  • Check the caller may do this — Application — The use case knows who is acting
  • Balance may not go negative — Domain — It is a business rule, true for every client
  • Price, tax, discount — Domain — Business calculations
  • The SQL query, the ORM mapping — Data access — It changes when storage changes
  • Retry a dropped connection — Data access — A storage detail

> [!TIP] > When the interviewer asks where something goes, say the question out loud and then the answer. Validation is the classic trap: format checks belong in presentation, business checks in the domain, and many systems need both.

What you will be able to do

  • Name the four layers, what each owns and what each must never contain.
  • Explain strict and relaxed layering and when a skip is acceptable.
  • Separate layers from tiers and draw one set of layers as two or three tiers.
  • Recognize the sinkhole and say when to accept it, open a layer, or merge layers.
  • Point to the domain-to-data arrow as the weak spot and fix it with the Dependency Inversion Principle.
  • Place any concern in its layer by asking which change would force it to change.

Interview questions

  • What is the difference between a layer and a tier?
  • Strict or relaxed layering: which would you choose, and when may a layer be skipped?
  • What is the architecture sinkhole anti-pattern, and what do you do about it?
  • In a classic layered design the domain depends on the persistence layer. Why is that a problem, and how do you fix it?
  • Where would you put validation, transactions and authorization in a layered system?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio