Programming with AI
ENRU
7.4

Reading backend code

routes, models, services and who calls whom

What it is

A backend project is almost always laid out in layers, and the layers are the same everywhere: routes, schemas, business logic, database work. Different frameworks name them differently, but they divide them the same way.

Knowing the division, you find what you need in an unfamiliar project in a minute instead of wandering for half an hour.

What it looks like in code

measured
app/
├── main.py              creating the app, wiring up the routers
├── routers/             routes: what lives at which address
│   ├── subs.py
│   └── users.py
├── schemas/             request and response schemas (article 7.3)
│   └── subs.py
├── services/            business logic: the domain rules
│   └── subs.py
├── repositories/        database work: queries only
│   └── subs.py
├── models/              table definitions
│   └── subs.py
└── core/
    ├── config.py        settings from the environment
    └── deps.py          shared dependencies: db connection, current user

Who calls whom

The rule is simple: calls go top to bottom and never back up.

measured
routers  →  services  →  repositories  →  database
LayerWhat is in itWhat must not be
routersparsing the request, calling a service, the response codeSQL, business rules
servicesthe rules: is it allowed, how many times, what nextSQL, HTTP codes
repositoriesdatabase queriesrules, HTTP
modelstable definitionslogic

Breaking that order is the most common sign of a neglected project. If repositories knows about HTTP codes, the layers are mixed up and changing that code means changing it everywhere at once.

How to find what you need

What the system can do — the list of routes (article 7.1).

Where a rule lives — in services. The question "why can't I subscribe twice" is settled there, not in the handler.

Where the database query is — in repositories. If you are hunting for where the data comes from, go there.

What comes in and goes out — in schemas.

Other names for the same things

HereIn other projects
routerscontrollers, views, handlers, api
servicesuse_cases, domain, logic, core
repositoriesdao, storage, db, queries
schemasdto, serializers, types

Different words, the same division. When you see an unfamiliar folder name, look it up in that table.

Dependencies

A line that appears in every handler:

@app.get("/subs/{user_id}")
def get_subs(user_id: int, conn = Depends(get_conn)):

Depends(get_conn) means: "framework, give me a database connection". Where it comes from and when it closes is the framework's concern.

Dependencies like that are the usual home for the database connection, token checking and the current user. If you don't know where an argument came from when it isn't in the address, look for Depends or its equivalent.

What to tell the AI

Prompt
Lay this project out by layers: what is in the routers, what is
in the services, what is in the database work. If the layers
are mixed up, say exactly where.
Prompt
I want to add a rule "no more than 10 subscriptions per user".
Which layer does it belong in and which files will have to be
touched? Don't write the code yet.

The second prompt is a test of architectural understanding: if the AI proposes writing the rule into the handler, the project's layers have already drifted.

What to look for

SQL in a handler. The most common sign. The query cannot be reused, and it cannot be tested without starting a server.

Business rules in a repository. if len(cities) > 10 among the SQL — a rule hidden where nobody will look for it.

HTTP codes in a service. raise HTTPException(404) outside a router means the service can no longer be called from anywhere but HTTP.

An import from the bottom up. repositories importing routers — a circular dependency and confusion.

A utils folder with everything in it. A sign that nobody looks for a home for new code any more (article 2.3).

Cheat sheet

  • Four layers: routes · services · repositories · models.

  • Calls go only top to bottom.

  • Rules in services, SQL in repositories, response codes in routers.

  • Folder names differ, the division is the same.

  • An argument that isn't in the address arrives through Depends.

  • A small project doesn't need layers — they appear with size.