What is the difference between strict (closed) layering and relaxed (open) layering, and what are the trade-offs of each?
answer
- closed = must traverse; open = may bypass
- layers of isolation vs. sinkhole
- 80/20 pass-through heuristic
- relaxed widens coupling, bypasses invariants
- selective openness: reads yes, writes no
basics
~20 sStrict layering lets a layer call only the layer immediately below it. Relaxed layering lets it call any lower layer, skipping levels. Strict gives better isolation and swappability; relaxed avoids pointless pass-through code but couples more layers together.
solid answer
~60 sIn **strict (closed) layering**, each layer may depend only on the one directly beneath it: presentation → application → domain → infrastructure, no skipping. Every request passes through every layer, so a change in one layer is absorbed by its immediate neighbour and cannot ripple further — the property sometimes called *layers of isolation*. In **relaxed (open) layering**, a layer may call any layer below it, so presentation can reach persistence directly for a read-only lookup. This removes delegation-only code and reduces mapping overhead, but now two layers depend on infrastructure, so an infrastructure change has a wider blast radius and the domain can be bypassed entirely. A common middle ground is *selectively open* layers: mark a specific layer open (for example a shared query/read layer) and keep the rest closed, documenting why. The decision hinges on whether the skipped layer adds real behaviour or is a pure pass-through: a layer that only forwards calls is a **sinkhole**, and if most requests sink through it, either open the layer or delete it.
go deeper
Define both: strict means you may only call the layer directly below; relaxed means you may skip levels. Give one pro and one con of each.
Explain layers of isolation and the sinkhole anti-pattern, and use the pass-through ratio to argue for collapsing or opening a layer.
Propose selective openness with an explicit, enforced policy — e.g. open read path via CQRS, closed write path — and discuss coupling blast radius and mapping cost.
Frame it as a governance question: the rule must be automatable, exceptions must be named and reviewed, and the choice should follow measured change patterns and performance data rather than dogma.
## Definitions A **layer** is a group of components sharing a technical role, placed at a position in an ordering. The style is defined not by the names of the layers but by the *dependency rule* between them. - **Closed layer (strict layering):** a request arriving at layer *N* must go to layer *N−1* next. Layer *N* may not reference layer *N−2*. Equivalently, each layer's only client is the layer directly above it. - **Open layer (relaxed layering):** a layer may be skipped; layer *N* may reference *N−1*, *N−2*, … Some authors call the layer that may be bypassed "open". Note the terminology inversion that trips people up: an **open layer is one you are allowed to bypass**; a **closed layer must be traversed**. "Closed" describes the layer's own boundary, not the permission of its callers. ## Why strict layering exists: layers of isolation The *layers of isolation* principle says a change confined to one layer should not force changes in others. Closed layers deliver this. If the persistence layer switches from an ORM to hand-written queries, only the layer immediately above it sees a change — and if its interface is preserved, nobody sees anything. If presentation were allowed to reach persistence directly, that same swap would hit two layers, and possibly every screen. Secondary benefits of strict layering: - A single, predictable path for cross-cutting concerns (transactions, authorization, auditing, caching) because everything funnels through the same seam. - A clean, acyclic module graph that supports incremental builds and independent testing. - Onboarding clarity: there is exactly one legal call path. ## Why relaxed layering exists: the sinkhole problem The **architecture sinkhole anti-pattern**: a request enters at the top, passes through layers that do nothing except forward the call, and returns. A `UserService.findById()` that only calls `UserRepository.findById()` and returns the result is a sinkhole hop. It costs a class, an interface, a mapping, a test, and reader attention, and buys nothing. A useful heuristic is the **80/20 check**: estimate the share of requests that are pure pass-throughs. If a small minority sink through, the layer earns its keep on the rest. If the large majority do, the layer is mostly ceremony — either open it for the pass-through cases, or collapse it. Relaxed layering also matters for performance: object mapping at every hop and per-layer copies of data can dominate cost for read-heavy paths. ## The cost of relaxing - **Wider coupling.** Every layer allowed to skip becomes a client of the deeper layer, so the deeper layer's interface is now load-bearing in more places and harder to change. - **Bypassable rules.** If presentation can hit persistence directly, the invariants, authorization checks, and auditing that live in the domain can be silently skipped by whoever is in a hurry. Today's "read-only shortcut" is tomorrow's write path. - **Erosion.** Once the rule is "you may skip when it makes sense," the rule effectively stops being enforceable, and the structure decays toward a call graph with no discernible shape. Strict rules are enforceable by tooling; discretionary ones are not. ## Practical middle grounds 1. **Selectively open one layer, documented.** For example, keep the domain closed for writes but allow a dedicated read/query path from presentation straight to a query service over the database — an application of **CQRS** (Command Query Responsibility Segregation: separate the model used for reads from the model used for writes). Reads have no invariants to protect, so bypassing the domain is far less dangerous than it is for writes. 2. **Delete the empty layer instead of skipping it.** If the application layer adds nothing today, collapse it and reintroduce it when there is behaviour to hold. 3. **Enforce mechanically.** Whatever rule you choose — strict, or strict-with-one-named-exception — encode it in build modules, package-visibility, or an architecture test so it is checked automatically rather than in review. ## Choosing - Prefer **strict** where invariants and cross-cutting concerns matter, where the team is large or changing, and where layers will genuinely be swapped. - Prefer **relaxed / fewer layers** for read-heavy, CRUD-dominated, latency-sensitive, or small systems where the intermediate layers demonstrably carry no logic. - Never relax silently: an undocumented shortcut is indistinguishable from an accident.
- Your service layer is a pure pass-through for 90% of endpoints. What would you do?Measure first: list the endpoints and see what the layer actually adds (transactions, authorization, mapping, orchestration). If those concerns live elsewhere and the layer only forwards, collapse it and let presentation use the repository abstraction directly, or keep it only for the 10% with real behaviour. Adding an empty layer 'for the future' is speculative and pays interest on every change.
- How would you allow a read-only bypass without letting writes escape the domain?Split the paths: expose a query-side API (read models, projections, or plain query services) that presentation may call directly, and keep all state-changing operations behind the domain layer. Enforce it — the query components expose no mutating operations, and an architecture test forbids presentation from referencing write repositories.
An airport with a closed security layer: every passenger walks through screening even if they are only changing terminals. Opening a bypass lane speeds up transfers but means the screening rules no longer apply to everyone — and once one bypass exists, more get requested.
saying these in an interview costs you the question
- Calling a layer 'open' when they mean 'must be used' — the terminology is reversed.
- Arguing strict layering is always right, without acknowledging pass-through cost and mapping overhead.
- Relaxing the rule case-by-case in code review with no written policy or automated check.
- Claiming skipping layers is fine because 'it's just a read' while the same shortcut is later used for writes.
- Adding an extra layer speculatively 'in case we swap the database' with no swap ever planned.