Первый Backend-проект часто начинается очень просто. Разработчик создает Python-файл, запускает сервер и добавляет несколько маршрутов. Пока приложение небольшое, такой подход кажется вполне удобным: весь код находится перед глазами, ничего не нужно искать по папкам, а новую функцию можно добавить за несколько минут.
Проблемы появляются позже. В приложении возникает регистрация пользователей, работа с базой данных, обработка ошибок, настройки, загрузка файлов и десятки маршрутов. Файл, который когда-то занимал сто строк, постепенно разрастается до тысячи. Одни функции начинают зависеть от других, одинаковая логика копируется между маршрутами, а небольшое изменение может неожиданно сломать совершенно другую часть приложения.
В этот момент становится важна структура Backend-проекта на Python. Хорошая структура нужна не ради красивого дерева папок. Ее задача — сделать так, чтобы разработчик понимал, где находится конкретная логика, мог изменять отдельные части приложения и не боялся роста проекта.
Разберемся, как организовать Backend-проект, какие слои действительно полезны, куда выносить бизнес-логику и почему не стоит начинать первое приложение с архитектуры из десятков директорий.
Почему Backend из одного файла сначала работает
Представим небольшой сервис заметок. Первая версия может выглядеть примерно так: project/ └── main.py В main.py находятся запуск приложения, несколько маршрутов и временное хранилище данных. Для первой версии это нормально. Если приложение содержит три маршрута и несколько функций, разделение на двадцать модулей только усложнит разработку.
Проблема возникает, когда в тот же файл начинают добавляться новые возможности: регистрация авторизация пользователи заметки файлы работа с базой валидация отправка писем настройки логирование Теперь изменение одного файла требует понимать практически все приложение.
Поэтому архитектура Backend-приложения обычно развивается вместе с продуктом. Не нужно создавать сложную структуру заранее, но важно вовремя заметить момент, когда один модуль перестает справляться со своей задачей.
Как понять, что проект пора разделять
Количество строк само по себе не является идеальным показателем. Файл на 500 понятных строк иногда удобнее десяти модулей по 50 строк с запутанными зависимостями. Лучше смотреть на ответственность кода. Если один файл одновременно принимает HTTP-запросы, проверяет права пользователя, выполняет вычисления, обращается к базе и отправляет письма, в нем смешано слишком много разных задач. Еще один признак — повторение.
Например, несколько маршрутов содержат одинаковую проверку: if user.balance < price: ... Если правило меняется, приходится искать все места, где оно использовано. Правильное разделение Backend-проекта на модули помогает собрать такую логику в одном месте и использовать ее повторно.
Что значит разделение ответственности
Один из основных архитектурных принципов можно сформулировать просто: разные части программы должны отвечать за разные задачи. Маршрут принимает запрос. Сервис выполняет бизнес-операцию. Слой работы с данными получает или сохраняет информацию. Модель описывает структуру данных. Конфигурация хранит настройки приложения. Это и есть разделение ответственности в Backend. Вместо одного большого процесса: HTTP request ↓
огромная функция со всей логикой ↓ HTTP response появляется последовательность: HTTP request ↓ Route ↓ Service ↓ Repository / Database ↓ Service ↓ Route ↓ HTTP response Каждый компонент выполняет более понятную задачу.
Базовая структура Backend-проекта
Для небольшого приложения структура может выглядеть так: project/ │ ├── app/ │ ├── main.py │ ├── routes/ │ ├── services/ │ ├── models/ │ ├── schemas/ │ ├── database/ │ └── config.py │ ├── tests/ ├── requirements.txt └── .env Это не универсальный шаблон, который нужно копировать в каждый проект. Но такая структура папок Backend-проекта хорошо показывает основную идею. main.py запускает приложение.
routes содержит HTTP-маршруты. services хранит бизнес-логику. models описывает сущности базы. schemas содержит структуры входных и выходных данных. database отвечает за взаимодействие с хранилищем. config.py собирает конфигурацию. Теперь рассмотрим каждый слой подробнее.
Что должно находиться в main.py
Одна из распространенных ошибок — продолжать писать всю логику в main.py, даже после создания других папок. В хорошо организованном приложении основной файл обычно остается относительно небольшим. Его задача — собрать приложение. Условно: app = create_application() app.include_router(users_router) app.include_router(products_router)
Здесь может выполняться настройка middleware, подключение маршрутов и регистрация общих обработчиков. Но организация main.py в Backend-проекте не предполагает размещение внутри него всей бизнес-логики. Основной файл должен скорее соединять компоненты, чем реализовывать каждую функцию продукта.
Зачем выносить маршруты отдельно
Представим интернет-магазин. В нем есть: /users /products /orders /payments Если все маршруты находятся в одном файле, он быстро становится неудобным. Логичнее разделить их по предметным областям: routes/ ├── users.py ├── products.py ├── orders.py └── payments.py Такое разделение маршрутов Backend позволяет быстрее находить нужный endpoint и уменьшает количество несвязанного кода в одном модуле.
При этом не нужно создавать отдельный файл для каждого маршрута. Обычно полезнее группировать связанные операции.
Что должен делать route
Route — это граница между HTTP и внутренней логикой приложения. Он получает запрос, извлекает необходимые данные, вызывает нужную операцию и формирует ответ. Например: POST /orders ↓ проверить формат входных данных ↓ вызвать create_order() ↓ вернуть результат Хороший route обычно не должен самостоятельно содержать десятки строк бизнес-правил.
Если endpoint одновременно проверяет остатки товара, рассчитывает скидку, списывает баланс, создает заказ и отправляет уведомление, его становится сложно читать и тестировать. Поэтому логика внутри Backend route должна быть ограничена задачами HTTP-уровня, а основные правила приложения лучше переносить дальше.
Что такое бизнес-логика
Бизнес-логика — это правила, определяющие поведение продукта. Например: пользователь не может купить товар при недостаточном балансе скидка действует только при определенных условиях заказ нельзя отменить после отправки бесплатный тариф имеет ограничение на количество проектов Эти правила не являются особенностями HTTP или базы данных. Они описывают сам продукт.
Поэтому бизнес-логика Backend-приложения не должна быть жестко привязана к конкретному endpoint. Если завтра то же действие потребуется выполнить из фоновой задачи или административного интерфейса, правило должно остаться тем же.
Service layer в Backend
Для бизнес-операций часто используют сервисный слой. Структура: services/ ├── user_service.py ├── order_service.py └── payment_service.py Например, order_service.py может содержать: def create_order(user, items): ... Внутри выполняются правила создания заказа. Service layer в Backend помогает отделить бизнес-операции от HTTP-маршрутов и непосредственной работы с базой.
Но сервисный слой не является обязательным архитектурным законом. Если приложение содержит пять простых операций, отдельная папка services может ничего не улучшить. Архитектура должна уменьшать сложность, а не создавать ее.
Почему не стоит писать бизнес-логику прямо в endpoint
Рассмотрим условный маршрут: @app.post("/orders") def create_order(data): user = ... product = ... if product.stock <= 0: ... if user.balance < product.price: ... price = ... discount = ... ... Сначала такой код выглядит нормально. Но затем создание заказа требуется использовать в другом месте. Разработчик либо копирует код, либо пытается вызвать route как обычную функцию. Оба варианта создают лишние зависимости.
Если вынести правила в сервис: order = order_service.create_order(...) маршрут становится только точкой входа. Именно поэтому вынос бизнес-логики из endpoint особенно полезен, когда одна операция используется несколькими частями приложения или начинает содержать большое количество правил.
Что такое repository layer
Следующий вопрос — где выполнять запросы к базе данных. В небольшом приложении service может напрямую обращаться к ORM. Это вполне допустимый вариант. В более крупном проекте иногда создают repository layer: repositories/ ├── user_repository.py ├── order_repository.py └── product_repository.py Repository отвечает за получение и сохранение данных. Например: user = user_repository.get_by_email(email) или:
order_repository.save(order) Repository layer в Backend позволяет отделить бизнес-операции от деталей хранения данных. Сервис говорит: «Найди пользователя по email». Repository решает, какой запрос для этого выполнить.
Нужен ли repository каждому проекту
Нет. Это важный момент, потому что начинающий разработчик легко превращает простое приложение в набор абстракций. Если service содержит: user = db.query(User).filter(...).first() и проект остается понятным, создавать repository только ради архитектурного шаблона необязательно.
Repository pattern в Python Backend становится полезнее, когда работа с данными сложная, повторяется в разных местах или ее действительно нужно отделить от бизнес-слоя. Дополнительный слой имеет цену: появляется больше файлов, функций и переходов между ними. Поэтому архитектурное решение должно окупать эту сложность.
Models и schemas — почему их разделяют
В Backend-проекте слово model может означать разные вещи. ORM model описывает структуру данных в базе: User Order Product Schema может описывать данные, которые приходят через HTTP или возвращаются клиенту. Например, база хранит: id email password_hash created_at is_admin Но при регистрации Frontend отправляет: email password А публичный ответ содержит: id email created_at Это три разных представления пользователя.
Поэтому разделение models и schemas помогает не смешивать внутреннюю структуру базы с форматом внешних данных.
Почему нельзя возвращать ORM-модель бездумно
Если объект базы автоматически превращается в HTTP-ответ, в нем могут оказаться поля, которые пользователь не должен видеть. Например: password_hash internal_status service_notes Отдельная response schema позволяет явно определить публичный формат. Поэтому схемы данных в Backend выполняют не только организационную, но и защитную функцию.
Разработчик контролирует, какие поля приложение принимает и какие возвращает наружу.
Где хранить работу с базой данных
Подключение к базе тоже не стоит размазывать по всему проекту. Обычно создается отдельный модуль или пакет: database/ ├── connection.py ├── session.py └── models/ Конкретная структура зависит от используемых библиотек. Основная идея организации базы данных в Backend-проекте состоит в том, чтобы конфигурация соединения и жизненный цикл подключения управлялись централизованно.
Если каждый модуль самостоятельно создает собственное подключение, контролировать ресурсы становится сложнее.
Где хранить настройки приложения
Backend почти всегда имеет конфигурацию: DATABASE_URL SECRET_KEY DEBUG ALLOWED_ORIGINS EMAIL_HOST Часть значений отличается между локальной разработкой и production. Поэтому конфигурация Backend-приложения должна быть отделена от основной бизнес-логики. Код может обращаться к объекту настроек: settings.database_url а реальные значения приходят из environment variables или другого конфигурационного механизма.
Так один и тот же код можно запускать в разных окружениях без постоянного редактирования исходников.
Почему секреты нельзя хранить в config.py
Отдельный config.py не означает, что туда нужно записать: SECRET_KEY = "my-super-secret-key" Если файл попадет в репозиторий, секрет окажется в истории проекта. Поэтому хранение настроек Backend и хранение секретных значений — не одно и то же. Модуль конфигурации может описывать, какие настройки нужны приложению, но чувствительные значения лучше получать из окружения или предназначенного для этого хранилища секретов.
Что хранить в .env
В локальной разработке часто используется .env. Например: DATABASE_URL=... SECRET_KEY=... DEBUG=true Файл помогает удобно передавать environment variables локальному приложению. Но использование .env в Backend-проекте требует осторожности. Файл с реальными секретами не должен случайно попадать в публичный репозиторий. Вместо него можно хранить пример: .env.example
с названиями необходимых переменных, но без настоящих секретов.
Как организовать зависимости между слоями
Одна из целей архитектуры — сделать направление зависимостей понятным. Например: Routes ↓ Services ↓ Repositories ↓ Database Route знает о service. Service знает о repository. Repository знает о базе. Но база не должна внезапно импортировать route. Такое разделение слоев Backend-приложения уменьшает циклические зависимости и делает движение по коду предсказуемым.
Если модули начинают импортировать друг друга во всех направлениях, структура папок уже не отражает настоящую архитектуру.
Что такое циклический импорт
Представим: users.py импортирует orders.py orders.py импортирует users.py Python может столкнуться с ситуацией, когда один модуль еще не успел загрузиться, а второй уже пытается получить из него объект. Так появляются циклические импорты в Python-проекте. Часто это не просто техническая проблема импорта, а признак того, что ответственность модулей разделена неудачно.
Вместо случайных обходных решений стоит посмотреть, почему два компонента настолько сильно зависят друг от друга. Возможно, общую логику нужно вынести в третий модуль.
Структура по техническим слоям
Один способ организовать проект — группировать файлы по их технической роли: app/ ├── routes/ ├── services/ ├── repositories/ ├── models/ └── schemas/ Это слоистая архитектура Backend. Она проста для понимания: если нужен route, разработчик идет в routes; если нужна бизнес-логика — в services. Для небольших и средних приложений такой подход часто достаточно удобен.
Но по мере роста появляется другая проблема: код одной функции продукта оказывается разбросан по пяти папкам.
Структура проекта по функциональности
Альтернативный вариант — группировать код по предметным областям: app/ ├── users/ │ ├── routes.py │ ├── service.py │ ├── repository.py │ └── schemas.py │ ├── orders/ │ ├── routes.py │ ├── service.py │ ├── repository.py │ └── schemas.py │ └── products/ ├── routes.py ├── service.py ├── repository.py └── schemas.py Это структура Backend по модулям или feature-based organization.
Теперь все, что связано с заказами, находится рядом. Такой подход может быть удобнее в большом проекте, где предметные области достаточно самостоятельны.
Слои или модули — что выбрать
Универсального ответа нет. Для первого приложения проще начать с технического разделения: routes services models schemas Если проект становится крупнее, можно перейти к организации по функциональным модулям. Поэтому вопрос как выбрать структуру Backend-проекта стоит решать исходя из размера и характера системы.
Главный критерий очень практичный: разработчик должен быстро понимать, где искать код конкретной функции и куда добавлять новую логику.
Что такое модульный монолит
Слово «монолит» иногда воспринимается как что-то устаревшее. На практике хорошо организованный монолит может быть отличной архитектурой для большого количества продуктов. В модульном монолите приложение запускается как единая система, но внутри разделено на относительно независимые области: Application │ ├── Users ├── Orders ├── Payments └── Notifications
Такой модульный монолит на Python позволяет сохранить простой deployment и одновременно поддерживать понятные границы внутри кода. Для первого серьезного Backend-проекта это часто разумнее, чем немедленно переходить к микросервисам.
Почему микросервисы не исправляют плохую структуру
Представим плохо организованный монолит, где логика перемешана между всеми компонентами. Если просто разделить его на пять серверов, архитектура не станет автоматически хорошей. Вместо внутренних зависимостей появятся сетевые: Service A ↓ HTTP Service B ↓ HTTP Service C Добавятся timeout, retry, deployment нескольких приложений, наблюдаемость и согласование данных.
Поэтому монолит или микросервисы для Backend — это не выбор между плохой и хорошей архитектурой. Хорошо организованный монолит обычно проще плохо спроектированной микросервисной системы.
Когда разделять Backend на сервисы
Отдельный сервис становится оправданным, когда существует реальная граница. Например, AI-компонент требует отдельного GPU и масштабируется независимо от основного Backend. Или тяжелая обработка файлов имеет совершенно другой профиль нагрузки. Тогда схема: Main Backend ↓ AI Service может быть логичной. Но разделение Backend на сервисы должно решать конкретную техническую или организационную проблему.
Если единственная причина звучит как «микросервисы современнее», скорее всего, усложнение преждевременно.
Где хранить интеграции с внешними сервисами
Приложение может работать с платежной системой, email-провайдером, картами или сторонним AI-сервисом. Если вызовы внешнего сервиса разбросаны по routes, заменить или протестировать интеграцию становится сложно. Полезно создать отдельный клиент: clients/ ├── payment_client.py ├── email_client.py └── ai_client.py Такое разделение внешних интеграций Backend изолирует код, который зависит от сторонней системы.
Бизнес-сервис вызывает понятную операцию, а детали HTTP-запроса к внешнему провайдеру остаются внутри клиента.
Где хранить общие функции
Во многих проектах появляется файл: utils.py Сначала в нем две функции. Через несколько месяцев — пятьдесят совершенно несвязанных операций. Это превращает utils.py в новое место, куда складывают код, для которого не смогли выбрать ответственность. Поэтому организация вспомогательных функций Python должна быть более осмысленной.
Если функции относятся к работе с изображениями, можно создать image_utils.py или отдельный модуль изображений. Если относятся к датам — модуль для работы с датами. Название должно помогать понять назначение кода.
Почему папка helpers не решает архитектуру
Та же проблема возникает с helpers. helpers/ может быстро стать складом случайных функций. Само создание папки не дает архитектурной границы. При организации кода Backend-приложения важнее определить ответственность компонента, чем подобрать красивое название директории. Хороший вопрос перед созданием файла: За какую конкретную часть системы он отвечает?
Если ответ звучит как «за разные полезные функции», граница слишком размыта.
Где хранить обработку ошибок
Ошибки могут возникать на разных уровнях. Repository не нашел запись. Service обнаружил нарушение бизнес-правила. Route должен превратить результат в корректный HTTP-ответ. Полезно различать эти ситуации. Например, бизнес-слой может сообщить: InsufficientBalance а HTTP-слой преобразует это в подходящий статус и сообщение.
Так обработка ошибок в архитектуре Backend не привязывает все внутренние функции напрямую к HTTP. Это особенно полезно, если бизнес-логику позже понадобится использовать вне веб-маршрутов.
Пользовательские исключения
Для доменных ошибок можно создавать собственные типы исключений: class InsufficientBalanceError(Exception): pass или использовать другой явный способ представления ошибок. Пользовательские исключения в Python Backend помогают отличать ожидаемую бизнес-ситуацию от настоящей программной ошибки. Недостаточный баланс и AttributeError — принципиально разные события, даже если оба прерывают обычное выполнение функции.
Где должна находиться валидация
Валидация бывает разной. Проверка: age должен быть integer email должен иметь корректный формат относится к структуре входных данных. А правило: пользователь младше 18 лет не может выполнить конкретную операцию может относиться уже к бизнес-логике. Поэтому валидация данных в Backend не всегда должна находиться в одном месте. Schema проверяет форму данных. Service проверяет правила продукта.
Разделение этих уровней делает код понятнее.
Dependency Injection в Backend
Когда сервис самостоятельно создает все свои зависимости, его сложнее тестировать и изменять. Например: def create_order(): repository = PostgresOrderRepository() Теперь функция жестко привязана к конкретной реализации. При dependency injection нужная зависимость передается извне. Упрощенно: def create_order(repository): ... Dependency Injection в Python Backend помогает уменьшить жесткую связанность компонентов.
Но здесь тоже важно не переусложнять. Не каждому небольшому проекту нужен отдельный DI-контейнер и сложная система интерфейсов. Сам принцип важнее конкретного инструмента.
Как структура помогает тестированию
Представим огромный endpoint, который одновременно работает с HTTP, базой, платежным сервисом и email. Чтобы проверить одно правило скидки, приходится запускать половину системы. Если правило находится в отдельном service, его можно протестировать гораздо проще. Поэтому архитектура Backend для тестирования тесно связана с разделением ответственности.
Хорошо отделенная бизнес-логика меньше зависит от инфраструктуры и легче проверяется отдельно.
Как структура помогает менять базу данных
На практике база меняется не каждый день, поэтому строить всю архитектуру только ради гипотетического перехода с PostgreSQL на другую СУБД не стоит. Но отделение доступа к данным имеет другое преимущество. Если способ получения данных сосредоточен в понятном месте, изменение схемы таблицы не требует искать SQL или ORM-запросы по всему проекту.
Именно поэтому слой доступа к данным Backend полезен прежде всего для контроля сложности внутри текущей системы.
Как структура помогает подключить AI
Представим приложение, которое вызывает модель машинного обучения. Плохой вариант — загружать модель и выполнять preprocessing непосредственно внутри большого route вместе с остальной логикой. Лучше разделить задачи: Route ↓ Service ↓ AI component ↓ Model Тогда интеграция ML-модели в Backend-архитектуру не ломает остальную структуру приложения.
Если позже модель переедет в отдельный сервис, бизнес-логике не обязательно полностью меняться. Изменится способ обращения к AI-компоненту.
Где хранить ML-модель в Backend-проекте
Если модель работает внутри того же приложения, можно выделить отдельный модуль: app/ ├── ai/ │ ├── model.py │ ├── preprocessing.py │ └── inference.py Здесь хранится логика загрузки модели, подготовки входных данных и prediction. Такое разделение ML и Backend-кода помогает не смешивать инфраструктуру веб-приложения с деталями машинного обучения.
Особенно это полезно, когда модель обновляется чаще или реже основного приложения.
Как организовать большой Full Stack проект
Если Frontend и Backend находятся в одном репозитории, структура может выглядеть так: project/ ├── frontend/ ├── backend/ ├── docs/ └── README.md Backend внутри имеет собственную структуру: backend/ ├── app/ ├── tests/ └── requirements.txt Так структура Full Stack проекта явно отделяет клиентскую и серверную части.
При этом общий репозиторий не означает, что Frontend должен знать внутреннее устройство Backend. Они взаимодействуют через определенный контракт.
Monorepo или отдельные репозитории
Frontend и Backend можно хранить вместе или отдельно. Monorepo удобно для небольших команд и проектов, где компоненты часто изменяются одновременно. Отдельные репозитории дают более независимые процессы разработки и доступа. Вопрос monorepo или отдельные репозитории не имеет одного правильного ответа.
Для первого Full Stack проекта единый репозиторий часто проще: меньше инфраструктуры, проще показать проект и легче синхронизировать изменения.
Как организовать тесты
Тесты полезно держать отдельно от production-кода или рядом с соответствующими модулями — оба подхода встречаются на практике. Простой вариант: tests/ ├── test_users.py ├── test_orders.py └── test_payments.py При росте можно повторить структуру приложения. Структура тестов Backend-проекта должна позволять быстро понять, какие функции системы проверяет конкретный набор тестов.
Не стоит создавать сложную иерархию тестов раньше, чем сами тесты стали достаточно многочисленными.
Где хранить миграции базы данных
Миграции являются частью проекта, потому что описывают развитие схемы базы. Их не стоит воспринимать как временные файлы, которые можно удалить после применения. Условно: project/ ├── app/ ├── migrations/ └── tests/ Организация миграций базы данных позволяет новому окружению последовательно привести пустую базу к актуальному состоянию. Кроме того, история миграций помогает понять, как менялась структура данных.
Как называть файлы и функции
Архитектура становится бесполезной, если разработчик не понимает названия. Файл: stuff.py почти ничего не сообщает. Функция: do_something() тоже. Лучше использовать названия, отражающие действие или ответственность: order_service.py calculate_order_total() Именование модулей Python-проекта — часть архитектуры, потому что хорошие названия уменьшают количество информации, которую приходится держать в голове.
Почему слишком много абстракций тоже плохо
Архитектурные статьи часто создают впечатление, что хороший Backend обязательно должен иметь: controllers services repositories interfaces adapters use_cases entities gateways factories Иногда такие слои действительно оправданы. Но для приложения из десяти endpoints это может превратить одну простую операцию в путешествие через восемь файлов.
Переусложнение архитектуры Backend увеличивает когнитивную нагрузку так же, как и отсутствие структуры. Хорошая архитектура не та, где больше слоев. Хорошая архитектура делает изменения проще.
Принцип YAGNI в архитектуре
YAGNI можно сформулировать как: You Aren't Gonna Need It. Не нужно заранее создавать инфраструктуру для проблем, которых пока нет. Например, если приложение использует одну базу и нет причин менять ее, необязательно строить сложную систему универсальных storage adapters. YAGNI в Backend-разработке помогает не тратить время на гипотетическую гибкость.
При этом это не означает, что код нужно писать хаотично. Нужно оставлять проект понятным, но не проектировать систему на десять лет вперед без реальных требований.
Когда пора делать рефакторинг структуры
Структуру стоит менять, когда текущая организация начинает мешать. Признаки:
- сложно найти код конкретной функции;
- один файл отвечает за слишком много задач;
- одинаковая логика постоянно копируется;
- изменение одной функции ломает несвязанные части;
- тестирование требует запуска слишком большого количества компонентов;
- импорты становятся запутанными;
- новые разработчики долго не могут понять проект.
В такой ситуации рефакторинг структуры Backend решает уже существующую проблему, а не гипотетическую.
Как рефакторить Backend постепенно
Необязательно останавливать разработку и переписывать весь проект. Можно начать с самого проблемного участка. Например, есть main.py на две тысячи строк. Сначала маршруты пользователей переносятся в users.py. Затем бизнес-логика регистрации выносится в user_service.py. После этого отдельно организуется работа с базой. Такой постепенный рефакторинг Python-проекта безопаснее полного переписывания.
После каждого изменения приложение остается рабочим, а структура постепенно становится понятнее.
Пример структуры небольшого Backend
Для небольшого учебного или первого production-проекта может быть достаточно: backend/ │ ├── app/ │ ├── main.py │ ├── routes/ │ │ ├── users.py │ │ └── products.py │ ├── models/ │ ├── schemas/ │ ├── services/ │ ├── database.py │ └── config.py │ ├── tests/ ├── .env.example └── requirements.txt Это структура небольшого Backend-проекта, которая не пытается заранее решить все возможные задачи.
Если приложение растет, ее можно расширять.
Пример структуры более крупного проекта
Когда предметных областей становится много, удобнее организовать код по модулям: app/ │ ├── users/ │ ├── routes.py │ ├── schemas.py │ ├── service.py │ └── repository.py │ ├── orders/ │ ├── routes.py │ ├── schemas.py │ ├── service.py │ └── repository.py │ ├── products/ │ ├── routes.py │ ├── schemas.py │ ├── service.py │ └── repository.py │ ├── database/ ├── common/ └── main.py
Такая структура большого Backend-проекта помогает держать код одной предметной области рядом. При этом внутри каждого модуля сохраняется разделение HTTP, бизнес-логики и доступа к данным.
Как выбрать архитектуру первого Backend-проекта
Для первого проекта лучше начать проще. Создайте отдельные маршруты для крупных частей приложения, вынесите модели и schemas, централизуйте подключение к базе и конфигурацию. Если route становится слишком большим — вынесите бизнес-логику в service. Если запросы к данным начинают повторяться и усложняться — подумайте о repository. Если один функциональный блок становится большим — превратите его в отдельный модуль.
Такой подход дает практический ответ на вопрос как организовать Backend на Python: структура должна появляться вслед за сложностью проекта, а не раньше нее.
Частые ошибки структуры Backend-проекта
Первая ошибка — хранить все в main.py. Вторая — противоположная: создать десятки папок до появления реальной логики. Третья — смешивать HTTP, бизнес-правила и запросы к базе в одной функции. Четвертая — создавать utils.py, куда постепенно попадает половина приложения. Пятая — допускать зависимости между модулями во всех направлениях.
Шестая — копировать архитектуру крупной компании в небольшой учебный проект без понимания причин ее появления. Седьмая — считать, что микросервисы автоматически делают приложение масштабируемым и правильно организованным. Такие ошибки архитектуры Backend-проекта возникают из одной крайности: либо структура отсутствует совсем, либо ее становится больше, чем самого продукта.
Чек-лист структуры Backend-проекта
Перед добавлением очередного архитектурного слоя полезно проверить текущий код. Можно ли быстро найти route конкретной функции? Отделены ли правила продукта от HTTP? Понятно ли, где происходит работа с базой? Настройки находятся в одном предсказуемом месте? Секреты не записаны в исходный код?
Не дублируется ли одна бизнес-логика между несколькими endpoints? Можно ли протестировать важное правило без запуска всего приложения? Понятно ли направление зависимостей между модулями? Если ответы в основном положительные, текущая структура, вероятно, уже выполняет свою задачу. Чек-лист архитектуры Backend нужен не для подсчета папок, а для проверки того, насколько удобно приложение изменять.
FAQ
Единственной правильной структуры нет. Она зависит от размера приложения, количества функций и команды. Для небольшого проекта обычно достаточно разделить routes, schemas, models, services и работу с базой. Нет. Если route содержит несколько простых действий, дополнительный слой может быть лишним. Service становится полезен, когда появляется заметная бизнес-логика или одна операция используется в нескольких местах.
Не обязательно. Его стоит добавлять, если работа с данными становится сложной или повторяется. Создавать repository для каждой простой ORM-операции только ради шаблона не требуется. Обычно отдельно от HTTP-слоя. В небольшом приложении это могут быть обычные функции, а в более крупном — services или отдельные предметные модули.
Создание приложения, подключение маршрутов, middleware и общую конфигурацию запуска. Основную бизнес-логику лучше размещать в соответствующих модулях. Для небольшого проекта структура по слоям часто проще. В крупном приложении организация по функциональным модулям может быть удобнее, потому что связанный код находится рядом.
Обычно нет. Хорошо организованный монолит проще разработать, протестировать и развернуть. Сервисы стоит выделять при появлении конкретной причины. Удобно изолировать его в отдельных clients или integrations, чтобы детали стороннего сервиса не распространялись по бизнес-логике и routes.
Когда текущая структура начинает мешать: код трудно искать, появляется много дублирования, файлы получают слишком много обязанностей или изменения становятся рискованными. Нет. Стоит предусмотреть понятные границы компонентов, но масштабировать архитектуру лучше на основании реальных требований и измерений.
Заключение
Хорошая архитектура Backend-проекта начинается не с количества папок и не с выбора модного паттерна. Она начинается с понимания ответственности каждой части приложения. HTTP-маршруты должны заниматься запросами и ответами. Бизнес-правила должны находиться там, где их можно использовать независимо от конкретного endpoint. Работа с данными должна быть организована так, чтобы запросы не были случайно разбросаны по всему проекту. Конфигурация и внешние интеграции тоже должны иметь понятные границы.
При этом архитектуру не нужно строить заранее для воображаемого продукта с миллионами пользователей. Небольшому Backend достаточно простой структуры. Когда появляется новая сложность, структура развивается вместе с ней. Именно такой подход позволяет сначала быстро создать рабочее приложение, а затем постепенно превратить его в Backend, который можно развивать без постоянного переписывания уже существующего кода.