# Layered Architecture

Organising a system into presentation, application, domain and infrastructure layers with a strict dependency direction.

**Leads to:** Database as Architectural Pillar, Dependency Inversion

**Rests on nothing.** A place to start.

*Software Engineering · Software Architecture*


## Why learn this

This unlocks the ability to understand why large software systems — including the code behind AI products — are structured the way they are. Without this, reading a real codebase feels like wandering through a city with no map. With it, you can find where a bug lives, what a change will break, and why a team put a database gate between you and your data.


## The intuition

Think of a building. It has a facade, interior walls, load-bearing pillars, and a foundation. A good building keeps the plumbing hidden, the electrical wiring out of sight, and the occupants safe. A bad building runs a water pipe through the middle of a bedroom because someone needed a tap there and it was cheaper at the time. Software is the same. A layered system separates the parts that people touch (the presentation), the parts that decide what the app actually does (the application and domain logic), and the parts that talk to the outside world like databases and servers (the infrastructure). The rule is that each layer only ever talks to the layer directly beneath it — it never reaches across. The presentation layer never touches the database; the application layer never writes a SQL query. This one rule, applied even by hand, prevents a thousand disasters: you can change the database without touching the screen, you can test the business rules without a real database, and when something breaks, you know which floor to go to. The cost is a little extra planning up front — but what you buy is a system that does not slowly rot into a tangled mess.


## An analogy

A government ministry. The public submits forms at the counter (presentation). The counter clerks do not decide policy — they just take the form and pass it to the case officers who apply the rules of the ministry (application). The actual rules of who gets what, written by legislators, are the domain. The ministry does not write its own electricity grid or run its own post — it uses the national infrastructure: the postal service, the power company, the tax records that live somewhere else. The strict rule is that the counter never goes straight to the power company to demand a favour, and the case officers never open the treasury vault themselves. Every request flows down one floor at a time. The upshot is that the ministry can switch from letters to email without changing the rules, and when the treasury changes its records system, the ministry's application logic does not care. Where the analogy breaks down: real government floors do not have written dependency rules, and they drift into chaos because people are impatient and take shortcuts. In software, the dependency rule is enforced by the compiler and the code review, not by a sign on a door. Also, a ministry has many floors with many shortcuts; a well-built software system is ruthlessly disciplined — no layer ever reaches over another, even if the shortcut is tempting.


## Definition

Layered architecture is a way of organising a codebase into distinct horizontal tiers — presentation, application, domain, and infrastructure — where each tier may only depend on the one beneath it, so that the outer tiers (user interface, data access) can change without disturbing the inner ones (business rules).


## How it connects

Your library lists this under Software Architecture, the parent concept of structural decisions. You already have notes on Separation of Concerns and Single Responsibility — this is their structural expression. Layering is the concrete realisation of 'keep distinct responsibilities in distinct places'. You also have notes on Hexagonal Architecture, which is a stricter version where all external interaction goes through defined interfaces instead of just a layer boundary, and Dependency Inversion, which is the mechanism that allows layers to depend on abstract rules rather than specific code details so the outer layer can be swapped without touching the inner. The 'ilities' — maintainability, testability, scalability — are the payoff this trade buys.


## The mathematics

The mathematics behind layering is not calculus or algebra — it is graph theory, the branch that studies connections between things. But you do not need to know that term. What matters is the idea of a dependency, which is a kind of arrow between two parts of a system: if part A uses part B, then A depends on B, and you draw an arrow from A to B. The rule of layering is a rule about these arrows: they may only point downward, from an outer layer to an inner one, never upward and never sideways across the same layer. This is like a one-way street. You can drive from the presentation layer down to the domain, but you can never drive up. Enforcing this rule is what keeps the system stable. If the arrows can go anywhere, then a change to any part can ripple anywhere else, and the system becomes impossible to reason about. With the rule, a change to the database cannot ripple up to the UI, because the UI never had a direct road to the database in the first place.

Let L1, L2, ..., Ln be the layers from outermost to innermost. The rule is: for any i < j, layer Li may depend on layer Lj only if j = i + 1. In words: each layer may only know the layer directly beneath it, never any deeper layer.


## A worked example

```
Consider a tiny system with three parts: a screen (P for presentation), a business rule (A for application), and a database (I for infrastructure). We will draw the allowed dependencies and the forbidden ones.

Step 1: Allowed: P → A, A → I   # The screen may use the application layer, and the application may use the infrastructure.
Step 2: Forbidden: P → I, A → P, I → A   # The screen must not touch the database directly, the application must not reach up to the screen, and the database must never depend on the application.
Step 3: Count the arrows: 2 allowed, 3 forbidden   # With three parts, there are 3 possible pairs, and each pair can have a dependency in either direction (or both). The rules cut most of them out.
Step 4: If we allow only downward arrows, the maximum number of arrows is 3 (P→A, P→I, A→I) but the strict rule forbids P→I, so the maximum is 2.   # The strict rule is even more restrictive than the general downward direction: outer layers may only know the layer directly beneath, not all inner layers.

Result: With 3 components and the strict rule, the system has exactly 2 allowed dependency arrows, forming a chain from top to bottom. This chain is what makes the system predictable.
```


## From scratch, in Python

```python
# Plain Python version of the layered rule
# We'll build a tiny order-processing system to show the rule.

# --- Infrastructure layer: talks to the database ---
class Database:
    def __init__(self):
        self.orders = []  # a list that will hold our orders

    def save(self, order):
        self.orders.append(order)
        return len(self.orders)  # return the order number

    def get(self, order_id):
        return self.orders[order_id - 1]  # lists are indexed from 0, so subtract 1

# --- Domain layer: the business rules, no database knowledge ---
def apply_tax(amount):
    return amount * 1.18  # 18% GST

# --- Application layer: uses domain for rules and infrastructure for storage ---
def place_order(database, item, amount):
    total = apply_tax(amount)  # call down to the domain, not up to the UI
    order = {'item': item, 'total': total}  # a dictionary: a named collection
    order_id = database.save(order)  # call down to infrastructure
    return order_id

# --- Presentation layer: what the user sees, only calls application ---
def user_interface():
    db = Database()
    order_id = place_order(db, 'chai', 120)
    print(f'Your order number is {order_id}')

# Run it
if __name__ == '__main__':
    user_interface()

# To prove the rule: in the presentation layer, we never call db.save() or apply_tax() directly.
# We only call place_order(). If we tried to call db.save() from the UI, we'd be breaking the rule.
# Uncommenting the next line would be a violation:
# print(db.orders)  # this would reach across two layers
```


## What the code does

We start with a Database class — that's the infrastructure layer. It has a list called orders that will hold order dictionaries. The save method appends a dictionary and returns the new order number. The get method retrieves one. We then define a plain function apply_tax — this is the domain layer. It applies the 18% GST to an amount. Note that this function has no idea about the database or the UI; it just knows the tax rule. The place_order function is the application layer. It takes the database, an item, and an amount, computes the total by calling apply_tax (a downward call to the domain), builds a dictionary (a named collection — think of it like a form with fields 'item' and 'total'), and saves it via database.save (a downward call to infrastructure). It returns the order number. Finally, user_interface is the presentation layer. It creates a Database, calls place_order, and prints the order number. The key point: the UI never touches the Database class directly, and it never calls apply_tax. The only thing it calls is place_order. If you tried to write db.save() inside the UI, you'd be reaching across layers — a violation. The code as written demonstrates the rule: each layer calls only the layer directly beneath it.


## Common misconceptions

**Layers are just folders on disk** — I organised my code into folders named presentation, application, domain, infrastructure, so I have a layered architecture.

Folders are cosmetic. The real architecture is the dependency arrows. You can put files anywhere and still have the UI call the database. Layering is a rule about who calls whom, not about where things are filed. In fact: Layering is enforced at the code level: the presentation layer may only import from the application layer, and each layer may only import from the layer directly beneath it. A folder structure that does not enforce that is just a filing system.

**More layers are always better** — If three layers are good, ten layers must be even better, because it's more organised.

Each layer adds complexity and a place for pass-through code that just forwards calls without adding any value. The books you have — Grokking Software Architecture — warn that rigid layering can produce pass-through code that does nothing but slow you down. In fact: Layers should exist only when they give you something: a place to enforce a rule, a boundary that isolates change, a spot where you can swap an implementation. If a layer just forwards every call unchanged, it is dead weight.

**The database must be on top** — The database is the most important part, so it deserves to be at the top, with everything else depending on it.

That would put infrastructure at the top, and everything would depend on a concrete technology. If you change databases, everything breaks. The whole point is to keep the domain independent. In fact: The domain is the core. The database is an implementation detail of the infrastructure layer, and it should be possible to swap it without touching the application logic. The schema is an architectural pillar, yes, but it lives in the infrastructure layer, not on top.

**Layering means I must duplicate code** — Since layers can't see each other, I have to copy the same function into every layer that needs it.

Copying code creates divergence and maintenance nightmares. The rule is about dependency direction, not about code sharing. In fact: Layers can share code through libraries and utilities that live in the appropriate layer. The rule is that a layer may depend on a lower layer, not that it cannot reuse shared components that are themselves properly placed.

**Layers are for big enterprise projects only** — My small app doesn't need layers; it's just a script in one file.

Small apps grow. The moment a script becomes a product, you will be adding features, fixing bugs, and changing the UI. Without layers, a small change to the database ripples through everything. In fact: Even a small app benefits from the discipline of separating concerns. It costs almost nothing to put the business rules in a function that does not know about the screen or the database. The layer is a mental boundary, not a folder tree.


## In short

Layered architecture is the rule that dependencies point inward: the UI calls the application layer, the application calls the domain, and the domain never hears about the infrastructure at the bottom. This one rule is what makes a large codebase navigable, testable, and resilient to change. Keep the layers thin, keep the arrows pointing down, and you can swap a database, a model, or a UI without touching the rest.


## Where this is used

Where does this show up in real AI and software work? Every serious system, in every corner of the industry.

**Large language models (LLMs).** The core model — the weights and the inference code that runs them — is the domain. That is where the actual intelligence lives. Around it, you build an application layer that orchestrates prompts, manages context windows, and decides when to call tools. The infrastructure layer is the GPU cluster, the tokeniser, the API that wraps the model. An LLM serving pipeline is a textbook layered system: your chat interface calls an orchestrator, which calls the model service, which calls the GPU driver — never the reverse. When a startup needs to replace the model behind its product, it can do so because the application layer depends on an abstract interface, not on the specific weights of GPT-4.

**Small language models on devices.** When a model has to run on a phone or a laptop, layering becomes critical. The presentation layer is the app UI; the application layer decides when to run the model locally versus sending to the cloud; the domain layer is the model itself, compiled to TensorFlow Lite or ONNX; the infrastructure layer is the on-device file system and the neural accelerator chip. If the app's UI reached straight into the model file, changing the model format would break the app. Because of layering, you can swap a lightweight model for a heavier one without touching the UI.

**Retrieval-augmented generation (RAG).** A RAG pipeline has a clear layered structure. The application layer orchestrates the query: it embeds the question, searches a vector database, and assembles the prompt. The vector database and the embedding model are infrastructure. The domain layer is the LLM that generates the answer. The presentation layer is whatever the user sees. The rule prevents the UI from querying the vector database directly, and it prevents the retrieval logic from being coupled to a specific embedding model. Developers write, in practice: an `EmbeddingService` interface, a `VectorStore` class, and a `RAGEngine` that sits on top and calls both. Each is in its own layer.

**Agentic systems.** Agents that plan, call tools, and act in loops are an application-layer concern. The agent orchestration code lives in the application layer; the tools (search, calculator, code runner) are infrastructure adapters; the policy or the model that decides the next step is the domain. The layered rule means the agent does not know the details of how a tool is implemented — it only knows the interface. This is how you can add a new tool without rewriting the agent. Developers write an `AgentLoop` class that calls a `ToolExecutor` interface; each tool is an adapter implementing that interface.

**Everyday applied machine learning.** Recommendation engines, fraud detection, forecasting — all of them follow the same pattern. The model training and inference is the domain; the feature engineering, data ingestion, and model serving are infrastructure; the application layer orchestrates the prediction requests; the presentation is a dashboard or API. When a bank swaps out a fraud model, it changes the domain layer and keeps everything else. When a team needs to test a new model, they write unit tests against the domain layer without a real database.

A developer building any of these systems writes code that enforces the rule: imports point downward, interfaces are defined in the lower layer, and dependency injection (a form of the Dependency Inversion you have notes on) passes concrete implementations in from the outside. What breaks when it is done badly? Mostly, testability. If the UI reaches into the database, you cannot test the UI without a database. If the application layer instantiates a specific model class, you cannot substitute a dummy model for testing. If the domain knows about the email library, you cannot run a transaction without sending an email. The rule is what keeps the system flexible and the tests fast.


---

Read this on the web: https://basavapurushottam.com/learn-ai/layered-architecture
