🧙 Садовник: уточнил шаги, добавил проверки и обоснования. Примите, если полезно.

#1
open+715proposed by gardener · Aug 8, 2026 · based on v1
Proposed changes · v1 → suggestion
+39172
1 ## Зачем знать варианты
2
3 Мы взяли слоистую структуру. Это осознанный выбор, а не единственный правильный вариант.
4
5 Знать остальные надо по трём причинам. Первая: ты придёшь в чужой проект, где всё устроено иначе, и надо будет понять логику, а не решить, что там бардак. Вторая: у каждой структуры есть цена, и лучше знать её заранее. Третья: вопрос про архитектуру задают на каждом собеседовании.
6
7 Этот список непоследовательный можно читать в любом порядке.
8 Плоская: всё рядом
9 ```
10 project/
11 ├── main.py
12 ├── models.py
13 ├── schemas.py
14 └── database.py
15 ```
16
17 Всё в корне, один файл на тип сущности. Именно так выглядит большинство примеров в туториалах.
18
19 **Когда годится:** скрипт, прототип, учебный пример, сервис на три эндпоинта.
20
21 **Где ломается:** примерно на тридцатом файле. `models.py` на две тысячи строк не читается, а двое людей правят его одновременно и постоянно конфликтуют.
22 why: Не надо стыдиться плоской структуры в маленьком проекте. Архитектура на вырост — тоже ошибка: пять слоёв ради двух эндпоинтов только мешают.
23 - [ ] Ты можешь назвать размер проекта, на котором плоская структура перестаёт работать
24Слоистая: группируем по технической роли
25 ```
26 app/
27 ├── models/ category.py product.py order.py
28 ├── schemas/ category.py product.py order.py
29 ├── repository/ category.py product.py order.py
30 ├── services/ category.py product.py order.py
31 └── routers/ category.py product.py order.py
32 ```
33
34 Папка — это **роль** файла. Все модели вместе, все роутеры вместе.
35
36 **Плюсы:** очевидно, куда класть новый файл. Правило зависимостей проверяется глазами. Новичок разбирается за десять минут.
37
38 **Цена:** чтобы добавить одну функцию, надо открыть пять папок и править пять файлов. Всё, что относится к заказам, размазано по всему проекту.
39 why: Это тот вариант, который выбрали мы. Сознательно: на нём проще всего понять саму идею разделения ответственности, потому что границы видны буквально в дереве папок.
40 - [ ] Ты можешь назвать главный минус слоистой структуры
41• По фичам: группируем по предметной области
42 ```
43 app/
44 ├── categories/
45 │ ├── models.py
46 │ ├── schemas.py
47 │ ├── service.py
48 │ └── router.py
49 ├── products/
50 │ └── ... то же самое
51 └── orders/
52 └── ... то же самое
53 ```
54
55 Папка — это **часть предметной области**. Слои никуда не делись — они внутри каждой папки.
56
57 **Плюсы:** вся функциональность в одном месте. Удалить фичу — удалить папку. Двое людей работают над разными фичами и не пересекаются.
58
59 **Цена:** границы слоёв больше не видны в дереве — их приходится держать договорённостью. Новичок легко напишет SQL в `router.py` — он же рядом.
60
61 Так устроен **[Netflix Dispatch](https://github.com/Netflix/dispatch)** и этот подход советует известный сборник **[FastAPI Best Practices](https://github.com/zhanymkanov/fastapi-best-practices)**.
62 why: Когда сущностей становится два десятка, слоистая структура начинает мешать: каждая задача требует прыгать между пятью папками. Группировка по фичам решает именно эту боль.
63 - [ ] Ты можешь объяснить, что теряется при переходе от слоёв к фичам
64• Гексагональная: порты и адаптеры [recommended]
65 ```
66 app/
67 ├── domain/ чистая логика, никаких библиотек
68 │ ├── entities.py
69 │ └── ports.py интерфейсы: «мне нужен тот, кто умеет сохранять»
70 └── adapters/ реализации этих интерфейсов
71 ├── postgres.py
72 ├── http.py
73 └── memory.py для тестов
74 ```
75
76 Идея: логика в центре и **не знает ничего** о базе, HTTP и фреймворке. Она объявляет порты — что ей нужно, а адаптеры это предоставляют.
77
78 **Плюсы:** логика тестируется без базы вообще. Смена PostgreSQL на что угодно — новый адаптер, логика не меняется.
79
80 **Цена:** много интерфейсов и перекладывания данных из одних объектов в другие. На маленьком проекте это чистые накладные расходы.
81
82 Первоисточник: **[Алистер Кокберн о гексагональной архитектуре](https://alistair.cockburn.us/hexagonal-architecture/)**
83 why: Главная идея — инверсия зависимостей: не логика зависит от базы, а база подстраивается под требования логики. Этот принцип полезен, даже если ты никогда не строишь полный гексагон.
84 - [ ] Ты можешь объяснить своими словами, что такое порт и что такое адаптер
85• Чистая архитектура: кольца вокруг сущностей [recommended]
86 ```
87 app/
88 ├── entities/ сущности и правила предметной области
89 ├── use_cases/ сценарии: «оформить заказ»
90 ├── adapters/ преобразование данных
91 └── frameworks/ FastAPI, SQLAlchemy, всё внешнее
92 ```
93
94 Правило одно: **зависимости всегда указывают внутрь**, к сущностям. Внешнее кольцо знает про внутреннее, никогда наоборот.
95
96 **Плюсы:** логика переживёт смену фреймворка и базы. Сценарии читаются как описание бизнеса.
97
98 **Цена:** самая высокая из всех. Много слоёв преобразования, больше кода ради того же результата. Оправдана там, где логика сложнее техники — банки, страхование, биллинг.
99
100 Первоисточник: **[Роберт Мартин о чистой архитектуре](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)**
101 why: Главное, что стоит вынести: чем важнее код, тем меньше он должен знать о том, что вокруг. Зачем — потому что вокруг меняется чаще всего.
102 - [ ] Ты можешь назвать тип проекта, где такая цена оправдана
103• Модульный монолит: фичи с границами [recommended]
104 ```
105 app/
106 ├── catalog/
107 │ ├── api.py единственная точка входа для чужих
108 │ └── internal/ внутренности, трогать нельзя
109 └── orders/
110 ├── api.py
111 └── internal/
112 ```
113
114 То же, что группировка по фичам, но с явным правилом: модули общаются **только через объявленный интерфейс**. Заказы не лезут во внутренности каталога, даже если технически могут.
115
116 **Плюсы:** готовность разрезать на микросервисы, если потребуется. Границы видны.
117
118 **Цена:** дисциплина. Python не мешает импортировать что угодно откуда угодно, и правило держится только на договорённости и ревью.
119 why: Модульный монолит — часто лучший ответ на «а не распилить ли на микросервисы?». Он даёт границы без сетевых вызовов, распределённых транзакций и десяти деплоев.
120 - [ ] Ты понимаешь, почему граница здесь держится не на Python, а на людях
121¶ ## Отдельное измерение: src-layout против flat-layout
122
123 Это **не архитектура**, а вопрос упаковки. Путают постоянно.
124
125 ```
126 flat-layout src-layout
127 project/ project/
128 ├── app/ ├── src/
129 │ └── ... │ └── app/
130 └── pyproject.toml └── pyproject.toml
131 ```
132
133 **Зачем src:** пакет нельзя случайно импортировать из корня проекта. Значит, тесты гоняются против **установленного** пакета, а не против файлов рядом. Если ты забыл добавить файл в сборку, тесты сразу покраснеют — а не у пользователя после релиза.
134
135 **Зачем без src:** короче пути, меньше вложенности. Для приложения, которое не публикуется как библиотека, разница невелика.
136
137 Мы выбрали flat с пакетом `app/` — потому что строим приложение, а не библиотеку на PyPI.
138
139 Подробно с аргументами с обеих сторон: **[src-layout vs flat-layout](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/)**
140¶ ## Что советует сам FastAPI
141
142 Официальная документация в разделе **[Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/)** показывает вариант ближе к слоистому: пакет `app/` с `routers/`, общими зависимостями и внутренними подпакетами.
143
144 Важное: **у FastAPI нет обязательной структуры.** В отличие от Django, который генерирует проект по шаблону и ждёт приложений с `models.py` и `views.py`, FastAPI не навязывает ничего. Отсюда и свобода, и разнобой между проектами.
145
146 Поэтому вопрос «как правильно» не имеет ответа в документации — только в контексте твоего проекта.
147¶ ## Как выбирать
148
149 | Ситуация | Что брать |
150 |---|---|
151 | Скрипт, прототип, до 10 файлов | Плоская |
152 | Учёба, небольшой сервис, до 10 сущностей | Слоистая |
153 | Много сущностей, команда от трёх человек | По фичам |
154 | Готовимся разрезать на сервисы | Модульный монолит |
155 | Логика сложнее техники, долгая жизнь | Гексагон или чистая |
156
157 **Три правила, которые важнее выбора:**
158
159 1. **Любая структура лучше, чем две сразу.** Половина по фичам, половина по слоям — хуже любого из вариантов по отдельности.
160 2. **Структура под размер сейчас, а не на вырост.** Перейти от слоёв к фичам потом проще, чем год тащить чистую архитектуру в сервисе из трёх эндпоинтов.
161 3. **Записанное решение важнее самого решения.** Абзац в README «мы выбрали так-то, потому что...» спасает следующего человека от переписывания всего по своему вкусу.
162
163
164
165
166¶ ## Что вынести
167
168 Нет «правильной» структуры — есть подходящая под размер проекта, размер команды и срок жизни.
169
170 Мы взяли слоистую, потому что на ней видно границы ответственности. Когда поймёшь, почему они важны, сможешь держать их в любой раскладке — именно поэтому мы и начинаем с неё.
171
172 И главное: в реальном проекте качество чаще определяется не выбором структуры, а тем, насколько последовательно её держат. Самая продуманная архитектура развалится за полгода, если каждый будет класть файлы туда, куда удобнее в момент.
1+ Начните с плоской структуры для небольших скриптов
2+ Размещайте все файлы (`main.py`, `models.py`, `database.py`) в корне проекта. Подходит для прототипов, автоматизаций и сервисов на пару эндпоинтов.
3+ why: Не надо стыдиться плоской структуры в маленьком проекте. Избыточная архитектура на вырост создаёт лишний шум.
4+ - [ ] Проверить, что количество файлов в корне не превышает 10-15
5+ - [ ] Убедиться, что над проектом не работают одновременно несколько разработчиков
6+• Перейдите к слоистой структуре по техническим ролям
7+ Группируйте файлы по их техническому назначению: `models/`, `schemas/`, `services/`, `routers/`. Это логичный шаг при выходе за рамки прототипа.
8+ why: Слоистая структура наглядно демонстрирует разделение ответственности и легко понятна новичкам.
9+ - [ ] Проверить явность направления импортов между слоями
10+ - [ ] Убедиться, что бизнес-логика не протекает в слой роутов
11+ Группируйте код по фичам при росте функциональности
12+ Создавайте отдельные модули под части предметной области (`users/`, `products/`, `orders/`), размещая слои внутри каждой фичи.
13+ why: Группировка по фичам локализует изменения: для добавления или удаления фичи работа происходит внутри одной папки.
14+ - [ ] Проверить, что фичи не зависят друг от друга напрямую без четкого интерфейса
15+ - [ ] Убедиться, что общение между фичами происходит через сервисы или события
16+ → Netflix Dispatch — https://github.com/Netflix/dispatch
17+ FastAPI Best Practices https://github.com/zhanymkanov/fastapi-best-practices
18+• Внедрите гексагональную архитектуру с портами и адаптерами [recommended]
19+ Отделите бизнес-логику в центр (`domain/`), описав внешние зависимости через интерфейсы-порты, а базы данных и фреймворки — через адаптеры.
20+ why: Инверсия зависимостей позволяет тестировать core-логику без запуска баз данных или сторонних сервисов.
21+ - [ ] Проверить, что доменная логика не содержит импортов SQLAlchemy, FastAPI или requests
22+ - [ ] Написать unit-тесты для домена с использованием memory-адаптеров
23+ Алистер Кокберн о гексагональной архитектуре https://alistair.cockburn.us/hexagonal-architecture/
24+Примените чистую архитектуру для сложной бизнес-логики [recommended]
25+ Организуйте проект концентрическими слоями: сущности (`entities`), сценарии использования (`use_cases`), адаптеры и внешние фреймворки. Зависимости направлены строго внутрь.
26+ why: Защищает критически важные бизнес-правила от изменений во внешних библиотеках и фреймворках.
27+ - [ ] Проверить соблюдение Dependency Rule: внутренние слои ничего не знают о внешних
28+ - [ ] Убедиться, что use_case принимает и возвращает DTO или entities, а не ORM-модели
29+ Роберт Мартин о чистой архитектуре — https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
30+ Изолируйте модули в формате модульного монолита [recommended]
31+ Скомбинируйте подход по фичам с жестким ограничением видимости: каждый модуль имеет открытый `api.py`, а внутренности скрыты в `internal/`.
32+ why: Даёт преимущества четких границ без сложности и сетевых overhead-расходов микросервисной архитектуры.
33+ - [ ] Настроить линтер или import-linter для запрета импортов из internal чужих модулей
34+ - [ ] Проверить возможность выделения модуля в отдельный сервис
35+• Выберите подходящую структуру под требования и масштабы проекта
36+ Оцените размер команды, сложность предметной области и частоту изменений. Начните с более простой структуры и усложняйте её по мере роста.
37+ why: Преждевременное усложнение архитектуры замедляет разработку так же сильно, как и ее полное отсутствие.
38+ - [ ] Сверить текущую сложность проекта с выбранной моделью
39+ - [ ] Обсудить с командой правила импортов и границы модулей
Review