Какие бывают структуры Python-проектов: не только слои

Плоская, слоистая, по фичам, гексагональная, чистая архитектура и модульный монолит. Что за каждой стоит, где она ломается и как выбрать под свой проект, а не под моду.

v1 0 stars 0 forks 0 watchers 1 branch 0 runs Public
mikicreated via APIv1
Зачем знать варианты

Мы взяли слоистую структуру. Это осознанный выбор, а не единственный правильный вариант.

Знать остальные надо по трём причинам. Первая: ты придёшь в чужой проект, где всё устроено иначе, и надо будет понять логику, а не решить, что там бардак. Вторая: у каждой структуры есть цена, и лучше знать её заранее. Третья: вопрос про архитектуру задают на каждом собеседовании.

Этот список непоследовательный — можно читать в любом порядке.

Плоская: всё рядом
code
1project/
2├── main.py
3├── models.py
4├── schemas.py
5└── database.py

Всё в корне, один файл на тип сущности. Именно так выглядит большинство примеров в туториалах.

Когда годится: скрипт, прототип, учебный пример, сервис на три эндпоинта.

Где ломается: примерно на тридцатом файле. models.py на две тысячи строк не читается, а двое людей правят его одновременно и постоянно конфликтуют.

Why: Не надо стыдиться плоской структуры в маленьком проекте. Архитектура на вырост — тоже ошибка: пять слоёв ради двух эндпоинтов только мешают.
Check
  • Ты можешь назвать размер проекта, на котором плоская структура перестаёт работать
Слоистая: группируем по технической роли
code
1app/
2├── models/ category.py product.py order.py
3├── schemas/ category.py product.py order.py
4├── repository/ category.py product.py order.py
5├── services/ category.py product.py order.py
6└── routers/ category.py product.py order.py

Папка — это роль файла. Все модели вместе, все роутеры вместе.

Плюсы: очевидно, куда класть новый файл. Правило зависимостей проверяется глазами. Новичок разбирается за десять минут.

Цена: чтобы добавить одну функцию, надо открыть пять папок и править пять файлов. Всё, что относится к заказам, размазано по всему проекту.

Why: Это тот вариант, который выбрали мы. Сознательно: на нём проще всего понять саму идею разделения ответственности, потому что границы видны буквально в дереве папок.
Check
  • Ты можешь назвать главный минус слоистой структуры
По фичам: группируем по предметной области
code
1app/
2├── categories/
3│ ├── models.py
4│ ├── schemas.py
5│ ├── service.py
6│ └── router.py
7├── products/
8│ └── ... то же самое
9└── orders/
10 └── ... то же самое

Папка — это часть предметной области. Слои никуда не делись — они внутри каждой папки.

Плюсы: вся функциональность в одном месте. Удалить фичу — удалить папку. Двое людей работают над разными фичами и не пересекаются.

Цена: границы слоёв больше не видны в дереве — их приходится держать договорённостью. Новичок легко напишет SQL в router.py — он же рядом.

Так устроен Netflix Dispatch и этот подход советует известный сборник FastAPI Best Practices.

Why: Когда сущностей становится два десятка, слоистая структура начинает мешать: каждая задача требует прыгать между пятью папками. Группировка по фичам решает именно эту боль.
Check
  • Ты можешь объяснить, что теряется при переходе от слоёв к фичам
Гексагональная: порты и адаптерыRecommended
code
1app/
2├── domain/ чистая логика, никаких библиотек
3│ ├── entities.py
4│ └── ports.py интерфейсы: «мне нужен тот, кто умеет сохранять»
5└── adapters/ реализации этих интерфейсов
6 ├── postgres.py
7 ├── http.py
8 └── memory.py для тестов

Идея: логика в центре и не знает ничего о базе, HTTP и фреймворке. Она объявляет порты — что ей нужно, а адаптеры это предоставляют.

Плюсы: логика тестируется без базы вообще. Смена PostgreSQL на что угодно — новый адаптер, логика не меняется.

Цена: много интерфейсов и перекладывания данных из одних объектов в другие. На маленьком проекте это чистые накладные расходы.

Первоисточник: Алистер Кокберн о гексагональной архитектуре

Why: Главная идея — инверсия зависимостей: не логика зависит от базы, а база подстраивается под требования логики. Этот принцип полезен, даже если ты никогда не строишь полный гексагон.
Check
  • Ты можешь объяснить своими словами, что такое порт и что такое адаптер
Чистая архитектура: кольца вокруг сущностейRecommended
code
1app/
2├── entities/ сущности и правила предметной области
3├── use_cases/ сценарии: «оформить заказ»
4├── adapters/ преобразование данных
5└── frameworks/ FastAPI, SQLAlchemy, всё внешнее

Правило одно: зависимости всегда указывают внутрь, к сущностям. Внешнее кольцо знает про внутреннее, никогда наоборот.

Плюсы: логика переживёт смену фреймворка и базы. Сценарии читаются как описание бизнеса.

Цена: самая высокая из всех. Много слоёв преобразования, больше кода ради того же результата. Оправдана там, где логика сложнее техники — банки, страхование, биллинг.

Первоисточник: Роберт Мартин о чистой архитектуре

Why: Главное, что стоит вынести: чем важнее код, тем меньше он должен знать о том, что вокруг. Зачем — потому что вокруг меняется чаще всего.
Check
  • Ты можешь назвать тип проекта, где такая цена оправдана
Модульный монолит: фичи с границамиRecommended
code
1app/
2├── catalog/
3│ ├── api.py единственная точка входа для чужих
4│ └── internal/ внутренности, трогать нельзя
5└── orders/
6 ├── api.py
7 └── internal/

То же, что группировка по фичам, но с явным правилом: модули общаются только через объявленный интерфейс. Заказы не лезут во внутренности каталога, даже если технически могут.

Плюсы: готовность разрезать на микросервисы, если потребуется. Границы видны.

Цена: дисциплина. Python не мешает импортировать что угодно откуда угодно, и правило держится только на договорённости и ревью.

Why: Модульный монолит — часто лучший ответ на «а не распилить ли на микросервисы?». Он даёт границы без сетевых вызовов, распределённых транзакций и десяти деплоев.
Check
  • Ты понимаешь, почему граница здесь держится не на Python, а на людях
Отдельное измерение: src-layout против flat-layout

Это не архитектура, а вопрос упаковки. Путают постоянно.

code
1flat-layout src-layout
2project/ project/
3├── app/ ├── src/
4│ └── ... │ └── app/
5└── pyproject.toml └── pyproject.toml

Зачем src: пакет нельзя случайно импортировать из корня проекта. Значит, тесты гоняются против установленного пакета, а не против файлов рядом. Если ты забыл добавить файл в сборку, тесты сразу покраснеют — а не у пользователя после релиза.

Зачем без src: короче пути, меньше вложенности. Для приложения, которое не публикуется как библиотека, разница невелика.

Мы выбрали flat с пакетом app/ — потому что строим приложение, а не библиотеку на PyPI.

Подробно с аргументами с обеих сторон: src-layout vs flat-layout

Что советует сам FastAPI

Официальная документация в разделе Bigger Applications показывает вариант ближе к слоистому: пакет app/ с routers/, общими зависимостями и внутренними подпакетами.

Важное: у FastAPI нет обязательной структуры. В отличие от Django, который генерирует проект по шаблону и ждёт приложений с models.py и views.py, FastAPI не навязывает ничего. Отсюда и свобода, и разнобой между проектами.

Поэтому вопрос «как правильно» не имеет ответа в документации — только в контексте твоего проекта.

Как выбирать
СитуацияЧто брать
Скрипт, прототип, до 10 файловПлоская
Учёба, небольшой сервис, до 10 сущностейСлоистая
Много сущностей, команда от трёх человекПо фичам
Готовимся разрезать на сервисыМодульный монолит
Логика сложнее техники, долгая жизньГексагон или чистая

Три правила, которые важнее выбора:

  1. Любая структура лучше, чем две сразу. Половина по фичам, половина по слоям — хуже любого из вариантов по отдельности.
  2. Структура под размер сейчас, а не на вырост. Перейти от слоёв к фичам потом проще, чем год тащить чистую архитектуру в сервисе из трёх эндпоинтов.
  3. Записанное решение важнее самого решения. Абзац в README «мы выбрали так-то, потому что...» спасает следующего человека от переписывания всего по своему вкусу.
В чём главное различие между группировкой по слоям и по фичам?
Почему не стоит брать чистую архитектуру для небольшого сервиса?
src-layout — это вариант архитектуры?
Что хуже всего?
Что вынести

Нет «правильной» структуры — есть подходящая под размер проекта, размер команды и срок жизни.

Мы взяли слоистую, потому что на ней видно границы ответственности. Когда поймёшь, почему они важны, сможешь держать их в любой раскладке — именно поэтому мы и начинаем с неё.

И главное: в реальном проекте качество чаще определяется не выбором структуры, а тем, насколько последовательно её держат. Самая продуманная архитектура развалится за полгода, если каждый будет класть файлы туда, куда удобнее в момент.