Chapter 10. Layered Architecture Style
The classic n-tier monolith: components organised into horizontal layers by technical role. Familiar, simple, and cheap — a good default starting point, but it scales poorly in agility, testability, and deployability as the application grows.
Topology
Components sit in logical horizontal layers, each with a specific role. There’s no fixed number, but the standard four are presentation, business, persistence, and database. Smaller apps may collapse business + persistence into three layers (e.g. SQL embedded in business components); larger ones may have five or more.
Physical (deployment) topology varies independently of the logical layers:
- Variant 1 — presentation + business + persistence in one deployable, with a separate physical database.
- Variant 2 — separate presentation, combined business + persistence, separate database.
- Variant 3 — all four combined (the database possibly in-memory).
This is a technically partitioned architecture: components are grouped by technical role (presentation, business), not by domain. So any one domain (e.g. “customer”) is spread across every layer, which makes domain changes awkward — and makes layered a poor fit for domain-driven design. The upside of separation of concerns is clear role/responsibility models and the ability to apply technical expertise per layer; the trade-off is reduced overall agility.
Layers of isolation
Each layer is closed or open:
- A closed layer can’t be skipped — a request must pass through it to reach the next layer down (presentation → business → persistence → database).
- This enables the layers of isolation concept: a change in one layer doesn’t affect others as long as contracts hold. Each layer is largely ignorant of the others’ internals.
For isolation to work, layers in the main request flow must be closed. If presentation could reach persistence directly, a persistence change would ripple into both business and presentation — tightly coupled, brittle, and expensive to change. Isolation also lets you swap a layer wholesale (e.g. replace a JSF presentation layer with React) without touching the others, assuming clean contracts (and the business delegate pattern).
Adding layers
Sometimes a layer should be open. Example: shared business objects (date/string utilities, auditing, logging) that the presentation layer must not use. Since presentation architecturally has access to the business layer, this is hard to govern. The fix is a new services layer holding the shared objects — now closed off from presentation by the business layer. But that services layer must be marked open, so the business layer can either use it or bypass it straight to persistence. Documenting which layers are open/closed (and why) is essential; failing to do so produces tightly coupled, brittle architectures.
Other considerations
- A good starting point when the eventual style is unknown (common in early microservices exploration where development must begin). Keep reuse minimal and inheritance trees shallow to preserve modularity and ease a later migration.
- Architecture sinkhole anti-pattern — requests that pass straight through every layer as simple pass-throughs with no business logic, causing needless object instantiation and processing (memory + performance cost). Every layered architecture has some; judge by the percentage. The 80-20 rule: ~20% sinkholes is fine; ~80% means layered is the wrong style. An alternative fix is to open all layers — at the cost of harder change management.
Why use this style
Good for small, simple applications or websites, and as a starting point under tight budget/time constraints. Its simplicity and familiarity make it one of the lowest-cost styles. Also good while still analysing requirements and unsure of the right style. As the app grows, maintainability, agility, testability, and deployability suffer — larger systems are usually better served by more modular styles.
Trade-offs
Characteristic ratings (1 star = poorly supported, 5 = a defining strength):
| Characteristic | Rating |
|---|---|
| Cost | High (cheap) — primary strength |
| Simplicity | High — primary strength |
| Reliability | 3 / medium |
| Testability | 2 |
| Performance | 2 |
| Deployability | 1 |
| Elasticity | 1 |
| Scalability | 1 |
| Fault tolerance | 1 |
| Number of quanta | Always 1 |
Reasoning behind the notable ratings:
- Cost & simplicity are the main strengths — monolithic, so no distributed complexity; easy to understand and cheap to build/maintain. But these ratings degrade as the monolith grows.
- Deployability & testability rate low: a three-line change forces a full redeployment (pulling in any DB/config/other changes), bundled with dozens of other changes, so risk is high and deployments infrequent. Most developers won’t run a full regression suite for a small change. Testability gets 2 (not 1) thanks to the ability to mock/stub components or whole layers.
- Reliability is medium (3) — no network traffic/latency/bandwidth issues — but capped by the monolithic deployment plus low testability and deployment risk.
- Elasticity & scalability rate 1 — monolithic deployment, no architectural modularity. Scaling parts of a monolith needs complex techniques (multithreading, internal messaging) the style isn’t suited to, and it’s always a single system quantum.
- Performance gets 2 — no parallel processing, closed layering, and sinkhole risk hurt it. Caching/multithreading can help but it’s not a natural fit.
- Fault tolerance rates 1 — one out-of-memory condition crashes the whole unit. Availability also suffers from high mean-time-to-recovery (startup of 2–15+ minutes for large monoliths).
Source
Mark Richards & Neal Ford, Fundamentals of Software Architecture, Chapter 10.