Layered Architecture
20 min
A layered architecture organizes code into ordered tiers — typically something like UI → Service (business logic) → Repository (data access) — where each layer only calls DOWNWARD into layers below it, never back upward. This keeps the foundation (data access) independent and reusable, and keeps changes to the UI from ever needing to touch business logic or storage code.
def validate_layering(calls, layers):
violations = []
for caller, callee in calls:
if layers.index(callee) < layers.index(caller):
violations.append((caller, callee))
return violations
layers.index(x) gives each layer's position (0 = topmost); a call is only valid when it goes to an EQUAL or HIGHER index (same layer, or strictly downward) -- this is the exact same 'compare positions in an ordered list' idea CI/CD's topological_order builds on, just checking a constraint instead of computing an order.
Write `validate_layering(calls, layers)`: `layers` is an ordered list from topmost to most-foundational (e.g. `['ui', 'service', 'repository']`). `calls` is a list of `(caller_layer, callee_layer)` pairs. A call is a VIOLATION if it calls a layer positioned ABOVE the caller in `layers` (i.e. calling backward, toward the top). Return the list of violating `(caller, callee)` pairs.
Why is a call from the repository layer back up to the service layer specifically considered an architectural violation?
You can validate a call graph against a layered architecture's one-directional dependency rule, catching violations that would couple foundational code to higher-level logic.