Layered Architecture
Software Architecture Fundamentals
Chapter 2 · Layered Architecture
Chapter 1 put exactly one boundary in front of storage — a Repository — and measured what that boundary bought. Layered architecture takes that same idea and generalizes it into a full stack: presentation (what the user sees or calls), business logic (the rules), and data (storage). This chapter verifies what goes wrong, concretely, when a layer's own boundary gets skipped — in either direction.
The Three Layers
| Layer | Owns | Should never contain |
|---|---|---|
| Presentation | Formatting output, accepting input | Business rules (discount math, validation logic) |
| Business | The rules — discounts, validation, calculations | Storage details (file formats, SQL, an ORM's own API) |
| Data | Reading and writing storage, exactly as it's asked | Business rules (a discount, a validity check) |
Reusing Chapter 1's own order-processing example, extended into all three layers:
Strict Layering, and What Skipping It Actually Costs
Strict layering means each layer only ever talks to the layer directly beneath it — presentation calls business, business calls data, and presentation never reaches past business straight into data. Relaxed layering deliberately allows presentation to call data directly for cases with genuinely no business logic involved (a simple read-only lookup, say) — a legitimate choice, if it's made deliberately. The version below isn't that: it's an accidental bypass of a layer that actually owns real logic.
100 from a loyalty member and displaying it two ways: display_order_total_correct() (going through OrderService) correctly reports $90.00 — the 10% loyalty discount applied. display_order_total_broken() (reading straight from OrderRepository) reports $100.00 — the exact raw, undiscounted number, because the one place that knew about the loyalty discount was never consulted. Both functions are correct code — neither raises an error — but one of them is silently wrong, purely because of which layer it talked to.
The Other Direction: When Business Logic Leaks Downward
Layer violations don't only run "upward, skipping down" — they can run the other way too, when a lower layer starts making decisions that belong to the layer above it.
100 for a loyalty member, then calling audit_raw_total() — a function whose entire purpose is reading the true, unmodified figure for accounting — returns 90.0, not 100. The data layer's own get() silently applied a discount before any caller ever saw the number, off by exactly 10.0 from the true value. There is no longer any way to ask this repository for the raw total at all — the business rule baked into the data layer took that option away from every caller, including ones that specifically needed it.
Where This Shows Up in Familiar Frameworks
Most web frameworks already impose some version of this split, even if the layer names differ — Django's models/views split, a typical Express app's routes/controllers/models split, and Rails' own MVC convention are all recognizable variants of presentation/business/data. Chapter 3 looks specifically at the presentation-side variant of this — MVC, MVP, and MVVM — in depth.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| One boundary (Chapter 1) generalized into three named layers | Chapter 7's Hexagonal Architecture takes the same boundary idea further — a business layer that doesn't even know which specific data layer it's talking to |
| Skipping a layer producing a silently wrong number, not a crash | Technical Support's own `appdiag1` — a wrong result with no error is exactly the class of bug that course's own diagnostic chapters are built to catch |
| Business logic leaking into the data layer, removing a caller's ability to get the raw value | Chapter 5's service-boundary criteria (coupling and cohesion) — this leak is a concrete instance of low cohesion inside the data layer |
Hands-On Exercises
Add a second business rule to OrderService.get_order_total(): a flat $5 off any order over $50, applied after any loyalty discount. Verify the correct presentation function reports the right total, and that the broken (layer-skipping) presentation function is now wrong by even more than before.
Fix this chapter's own OrderRepositoryBad by moving its loyalty-discount logic out of get() and into a proper OrderService-style business layer, following this chapter's own OrderRepository/OrderService shape. Verify audit_raw_total() now correctly returns the true raw value.
This chapter's own text distinguishes a deliberate relaxed-layering choice (presentation reading directly from data for something with no business logic) from an accidental bypass (this chapter's own broken example). Using this chapter's own OrderRepository, write one new read-only method that would be genuinely safe for presentation to call directly, and explain specifically why it's safe where display_order_total_broken() wasn't.
Chapter 2 Quick Reference
- Three layers: Presentation (formatting/input), Business (the rules), Data (storage) — each should own exactly one kind of decision
- Verified — skipping upward: reading straight from the data layer instead of going through the business layer reported
$100.00instead of the correct$90.00— a silently wrong number, not a crash - Verified — leaking downward: baking a business rule into the data layer made the true raw value (
100) permanently unreachable — an audit function needing it got90.0instead, with no way to ask for the real number - Next chapter: MVC and Its Variants — the presentation-side version of this same layering question