Перейти к содержимому
Назад
16 мин чтения

Айсберг IT-проекта: что скрыто под капотом любого приложения

Гайдбук для продакта: 9 невидимых слоёв за каждой кнопкой и почему «полчаса» оценки превращаются в неделю работы.

Engineering Product Design Software Architecture Estimation Guide

Айсберг IT-проекта: что скрыто под капотом любого приложения

Гайдбук для продакта

На примере кодовой базы образовательной платформы (Next.js + FastAPI + PostgreSQL).


Зачем этот документ

Когда кто-то говорит «да тут делов на полчаса», он смотрит на верхушку айсберга — на экран, кнопку, текст. Это то, что видно. Но под водой — в 7–8 раз больше: база данных, контракты между сервисами, очереди, логи, тесты, инфраструктура. Эти вещи не видит никто из пользователей, но они есть в каждом приложении, и именно они превращают «полчаса» в неделю.

Этот документ — карта подводной части. Цель — чтобы ты мог:

  • аргументированно защищать сроки и бюджет («это не 30 минут, потому что меняется БД, а БД тянет за собой контракт, тесты и деплой»);
  • понимать, что делает команда (и кодинг-ассистент), даже не умея писать код;
  • оценивать риск, если завтра уйдёт человек, который «знал, как тут всё устроено».

Масштаб на цифрах нашего проекта. Пользователь видит экраны — это часть из 183 файлов фронтенда. Но рядом живут 70 файлов бэкенда, 62 файла тестов, 6 миграций базы, 5 контейнеров инфраструктуры, 9 фаз планирования с 86 планами. Бóльшая часть проекта — под водой.


Часть 1. Карта айсберга

        ╔══════════════════════════════╗
        ║   НАД ВОДОЙ (видит клиент)   ║   ← экраны, тексты, кнопки,
        ║   ~10–15% работы             ║     анимации, контент курса
        ╠══════════════════════════════╣
        ║                              ║
        ║   ПОД ВОДОЙ (не видит никто) ║   1. API-контракты
        ║   ~85–90% работы             ║   2. База данных и миграции
        ║                              ║   3. Аутентификация и безопасность
        ║                              ║   4. Фоновые задачи (очереди)
        ║                              ║   5. Логи и наблюдаемость
        ║                              ║   6. Тесты
        ║                              ║   7. Инфраструктура и деплой
        ║                              ║   8. Конфигурация и секреты
        ║                              ║   9. Обработка ошибок и интеграции
        ╚══════════════════════════════╝

Дальше — по каждому слою: что это, что конкретно у нас в проекте, что сломается без этого.


Часть 2. Девять подводных слоёв

1. API-контракты — договор между фронтендом и бэкендом

🧩 Что это. Фронтенд (то, что в браузере) и бэкенд (то, что на сервере) — две разные программы на разных языках. Чтобы они понимали друг друга, нужен строгий «договор»: какие поля, какого типа, обязательные или нет. Это и есть API-контракт. Аналогия: бланк договора аренды — обе стороны заполняют одни и те же поля в одном и том же формате, иначе сделка не состоится.

Что у нас. Бэкенд описывает все формы данных в Pydantic-схемах (backend/app/schemas/ — auth, payments, health, progress…). Из них FastAPI автоматически генерирует OpenAPI-схему — машинночитаемое описание всего API. А фронтенд через инструмент hey-api автоматически превращает её в типизированный SDK (frontend/src/lib/api/types.gen.ts). То есть контракт не пишется руками дважды — он генерируется из одного источника.

Что сломается без этого. Бэкенд переименовал поле user_iduserId, фронтенд об этом не узнал → страница падает с белым экраном. Без контракта такие рассинхроны ловятся только в проде, руками пользователей. С контрактом — несоответствие всплывает сразу при сборке.

Почему это есть в любом проекте. Как только у вас больше одной программы (а это почти всегда), им нужен общий язык. Контракт — это не «бюрократия», это страховка от того, что две половины приложения разойдутся.


2. База данных и миграции — память приложения и её эволюция

🧩 Что это. База данных (БД) — это «память» приложения: где хранятся пользователи, прогресс, платежи. Миграция — это пронумерованный, версионированный шаг изменения структуры этой памяти. Аналогия: БД — это здание, а миграции — папка с поэтажными планами перестроек, строго по порядку. Нельзя «просто подвинуть стену» — нужен план перестройки, который можно накатить и (в идеале) откатить.

Что у нас. Структуру данных описывают модели SQLAlchemy (backend/app/models/ — User, Payment, Topic, UserProgress…). Эволюцию структуры хранят 6 миграций Alembic (backend/alembic/versions/): 001 — начальная схема, 002 — админы, 004 — бейджи, 005 — платежи и рефералы, 006 — таблицы фоновых задач. Каждая новая фича, которой нужно «новое поле» — это новая пронумерованная миграция.

Что сломается без этого. Разработчик добавил колонку у себя на ноутбуке, но не оформил миграцию → на сервере этой колонки нет → приложение падает при первом же запросе. Или: накатили изменение, оно сломало данные, а отката нет → восстанавливаем из бэкапа вручную (если бэкап есть).

Почему это есть в любом проекте. Данные живут дольше кода. Структура данных всегда меняется со временем, и эти изменения должны быть воспроизводимыми и согласованными между ноутбуком разработчика, тестовым стендом и продом.


3. Аутентификация и безопасность — замки и проверка «кто внутри»

🧩 Что это. Аутентификация — «кто ты». Авторизация — «что тебе можно». JWT-токен — это как браслет в отеле «всё включено»: на нём зашифровано, кто ты и до какого числа действует, и его нельзя подделать, не зная секретного ключа отеля.

Что у нас. Вход — по коду доступа (без email/пароля). После входа выдаётся JWT в cookie с настройками HttpOnly (не украсть скриптом), Secure (только по HTTPS), SameSite (защита от подделки запросов). Защита от перебора кодов — SlowAPI ограничивает до 5 попыток за 15 минут с одного IP (backend/app/middleware/rate_limit.py). Токен пользователя и токен админа — разные cookie (session и admin_session), чтобы обычный юзер не дотянулся до админки. Проверка токена встроена в backend/app/dependencies/auth.py и срабатывает на каждом защищённом запросе.

Что сломается без этого. Нет rate-limit → коды доступа подбирают ботом за ночь. Cookie без HttpOnly → токен крадут через уязвимость на странице. Нет разделения user/admin → дыра в админку. Это не «фичи на будущее» — это то, на чём проект либо стоит, либо падает в первый же день после публикации.

Почему это есть в любом проекте. Как только приложение в интернете — его атакуют. Не «если», а «когда». Безопасность — это не отдельная задача в конце, это слой под каждой кнопкой.


4. Фоновые задачи (очереди) — то, что происходит без пользователя

🧩 Что это. Некоторые действия нельзя делать «прямо сейчас, пока пользователь ждёт» — они долгие или могут не получиться с первого раза (отправка сообщения, письма, обработка платежа). Их складывают в очередь задач — список дел, который отдельный «работник» разбирает в фоне. Аналогия: на почте вы не ждёте у окна, пока посылка доедет — вы оставляете её, и дальше она едет сама, а если самолёт задержали — её отправят следующим рейсом.

Что у нас. Очередь — Procrastinate (backend/app/tasks/), и она работает прямо в PostgreSQL. После успешной оплаты ставится задача send_purchase_confirmation — отправить пользователю код доступа в Telegram. У задачи 8 попыток с нарастающей паузой: если Telegram временно недоступен, она не теряется, а повторится позже.

Что сломается без этого. Без очереди: пользователь оплатил → бэкенд пытается отправить сообщение прямо в момент оплаты → Telegram моргнул → сообщение потеряно → клиент заплатил и не получил код → жалоба и возврат. Очередь превращает «потеряли навсегда» в «доставим со второй попытки».

Почему это есть в любом проекте. Внешний мир ненадёжен. Любое действие «наружу» рано или поздно не пройдёт с первого раза — очередь это переживает, прямой вызов нет.


5. Логи и наблюдаемость — чёрный ящик самолёта

🧩 Что это. Логи — записи о том, что приложение делало («в 14:03 пользователь X вошёл», «в 14:05 платёж Y отклонён»). Наблюдаемость (observability) — это логи + метрики (графики «сколько запросов в секунду») + алерты (уведомление «сервер упал»). Аналогия: чёрный ящик и приборная панель самолёта. Пока всё хорошо — на них не смотрят. Когда что-то сломалось — это единственный способ понять, что произошло, не будучи свидетелем.

Что у нас. Структурированное логирование — structlog (backend/app/main.py): логи пишутся не «простынёй текста», а как данные с полями (payment_id, error_type), которые потом можно фильтровать и искать. Есть health-эндпоинт /api/health — «пульс» приложения, по которому Docker понимает, живой контейнер или надо перезапустить.

⚠️ Реальный пример «невидимой работы» прямо из проекта. Полноценный мониторинг — Prometheus (метрики), Grafana (дашборды), Sentry (трекинг ошибок), поиск.

Что сломается без этого. Без логов отладка бага звучит так: «у кого-то что-то не работает, повторить не можем, причину не знаем». С логами: «вот точная строка, вот данные, вот причина». Без мониторинга вы узнаёте о падении от разъярённого клиента, а не от алерта за 2 минуты до того, как заметят остальные.

Почему это есть в любом проекте. Приложение, которое нельзя «прослушать», нельзя и чинить — только гадать. Чем больше пользователей, тем дороже каждая минута «гадания».


6. Тесты — страховочная сетка

🧩 Что это. Тест — это маленькая программа, которая автоматически проверяет, что другая программа работает правильно. Аналогия: краш-тесты на автозаводе. Машину бьют о стену сотни раз до того, как её купит человек, — чтобы поломка нашлась на полигоне, а не на трассе.

Что у нас. Три уровня: pytest — 31 файл тестов бэкенда (платежи, авторизация, геймификация, вебхуки); Vitest — тесты компонентов фронтенда; Playwright — E2E-тесты, которые реально открывают браузер и кликают как пользователь, включая визуальную регрессию (сравнение скриншотов с эталоном с точностью 0.1%). Плюс инфраструктура тестов: фикстуры и фабрики (backend/tests/conftest.py, fixtures/) — заранее заготовленные «тестовые пользователи» и «тестовые платежи».

Что сломается без этого. Без тестов каждое изменение — это лотерея: починили одно, втихую сломали три других, узнали об этом от пользователей. Тесты — это то, что позволяет вообще что-либо менять в зрелом проекте без страха. Парадокс: тесты выглядят как «лишняя работа», но именно они делают всю последующую работу быстрой и безопасной.

Почему это есть в любом проекте. Без сетки безопасности либо двигаешься медленно и со страхом, либо быстро и с регулярными авариями. Тесты — единственный способ двигаться быстро и безопасно одновременно.


7. Инфраструктура и деплой — фундамент, на котором всё стоит

🧩 Что это. Инфраструктура — серверы, базы, прокси, хранилища, на которых крутится приложение. Docker / контейнеры — способ упаковать каждую часть приложения в стандартную «коробку», которая одинаково работает на ноутбуке разработчика и на боевом сервере. Деплой — процесс выкатки новой версии на сервер. CI/CD — конвейер, который автоматически проверяет и собирает код при каждом изменении.

Что у нас. docker-compose.yml описывает 5 контейнеров: база (PostgreSQL), бэкенд (FastAPI), фронтенд (Next.js), прокси (Caddy — раздаёт HTTPS и направляет запросы), хранилище файлов (MinIO). CI-конвейер (.github/workflows/ci.yml) на каждое изменение прогоняет 5 проверок: линтеры фронта, линтеры бэка, тесты фронта, тесты бэка, сборку Docker-образов. Dockerfile’ы многоступенчатые — отдельно режим разработки, отдельно облегчённый прод-образ под непривилегированным пользователем (вопрос безопасности).

⚠️ Бэкапы — ещё один «невидимый» пункт. Автоматизация резервного копирования. Бэкап — не «сделали и забыли»: его нужно настроить, проверять, что он реально создаётся, и периодически тренировать восстановление. Бэкап, который ни разу не разворачивали, — это не бэкап, а надежда.

Что сломается без этого. Без контейнеров — классическое «у меня на ноутбуке работает», а на сервере нет. Без CI — сломанный код доезжает до прода. Без продуманного деплоя — выкатка превращается в ручной ритуал из 15 шагов, где один пропущенный шаг = даунтайм.

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


8. Конфигурация и секреты — рубильники и ключи

🧩 Что это. Конфигурация — настройки, которые отличаются между «разработкой» и «продом» (адрес базы, режим отладки). Секреты — пароли, токены, ключи API, которые нельзя хранить в коде. Аналогия: код — это чертёж дома, одинаковый для всех. Конфиг — какой счётчик и какой адрес у конкретного дома. Секреты — ключи от этого дома, которые не печатают на чертеже.

Что у нас. Все настройки — в файле .env (в репозиторий не попадает), а .env.example — это «бланк» с ~25 переменными-заглушками, который видно всем. Бэкенд читает их через backend/app/config.py. Отдельный приятный нюанс: там есть предохранитель validate_production_fiscal_config() — приложение откажется стартовать в проде, если в нём остались тестовые ключи платежей или адрес localhost. То есть конфиг сам не даёт выкатить «недонастроенную» систему.

Что сломается без этого. Секрет в коде → он навсегда в истории Git → утечка ключей платёжной системы. Перепутали dev- и prod-конфиг → приложение в проде ходит в тестовую базу. Это классические дорогие аварии, и весь слой существует, чтобы их не было.

Почему это есть в любом проекте. Одно и то же приложение должно по-разному вести себя в разных средах, и при этом ключи не должны «утекать». Это два требования, которые есть всегда.


9. Обработка ошибок и интеграции — что происходит, когда что-то идёт не так

🧩 Что это. Обработка ошибок — заранее продуманные ответы приложения на сбой (а не «упало с непонятным экраном»). Интеграция — связь с чужим сервисом (Telegram, платёжная система), который вы не контролируете.

Что в коде у приложения. У нас две платёжные интеграции — Tinkoff и YooKassa (одна как запасная), плюс Telegram-бот на aiogram. Реальные тонкости, которые «не видно», но которые заняли время:

  • Идемпотентность. Платёжная система может прислать уведомление об одной оплате дважды. В базе стоит ограничение UNIQUE(provider, payment_id) — повторное уведомление просто игнорируется, и пользователь не получит два кода / не оплатит дважды.
  • Порядок операций. Сначала фиксируем платёж в базе, только потом ставим задачу «отправить код». Иначе фоновый работник может проснуться раньше, чем данные сохранились.
  • Мягкая деградация. Если поставить фоновую задачу не удалось, но платёж уже зафиксирован — мы логируем ошибку и отвечаем платёжной системе «ОК», чтобы она не заваливала нас повторами. Сбой обрабатывается осознанно, а не «как получится».

Что сломается без этого. Без идемпотентности — двойные списания и жалобы. Без продуманного порядка — «оплата прошла, кода нет». Без обработки ошибок интеграции — один моргнувший внешний сервис роняет весь ваш функционал.

Почему это есть в любом проекте. Любой проект рано или поздно зависит от чужих сервисов, а чужие сервисы ломаются, дублируют, отвечают не вовремя. Половина работы интеграции — это не «счастливый путь», а аккуратная обработка всех «несчастливых».


Часть 3. Эффект домино — почему «полчаса» становятся неделей

Возьмём реальный пример: «давайте добавим в профиль поле “Должность”».

Смотрим, чего «поле» касается на самом деле:

#СлойЧто нужно сделатьЭто «полчаса»?
1База данныхНовая колонка → новая миграция 007Нет — нужно написать и проверить откат
2МодельПравка модели User в коде бэкенда~быстро
3API-контрактДобавить поле в Pydantic-схему + валидация (длина, формат)~быстро
4ЭндпоинтГде это поле принимается, где отдаётся, кто его может менятьНет — это логика
5БезопасностьПроверка: может ли пользователь менять только своё поле, а не чужоеНет — это важно
6Генерация SDKПерегенерировать TypeScript-клиент, иначе фронт «не знает» о поле~быстро, но обязательно
7ФронтендФорма ввода + отрисовка, строго по дизайн-системеНет — это вёрстка + состояние
8Логи / приватностьПроследить, чтобы поле не попало в логи — у проекта требование Zero PIIНет — это юридический риск
9Тестыpytest (бэк) + Vitest (фронт) + Playwright (E2E) + новый эталон скриншотаНет — это половина работы
10CIВсё это должно пройти 5 проверок конвейераавтоматически, но чинить падения — время
11ДеплойНакатить миграцию на проде аккуратно, иметь план откатаНет — это риск даунтайма

Итог: «полчаса на поле» — это 9–11 точек касания в 6–8 слоях айсберга. Честная оценка — не «полчаса», а 1–3 дня для аккуратного результата. И это не раздувание бюджета — это и есть настоящая стоимость. «Полчаса» — это стоимость только верхушки, пункта 7.

Как это говорить заказчику. Не «вы не понимаете, это сложно». А: «визуально — да, это одно поле, 30 минут вёрстки. Но поле живёт в базе — это миграция; база связана контрактом с фронтендом — это перегенерация; всё это закрывается тестами — это ещё столько же; и есть требование не хранить персональные данные — это поле надо проверить отдельно. Поэтому честно — это 2-3 дня. Я могу показать, из чего складывается каждый».

И еще… Там не всегда все с первого раза заводится (баги) и еще технический долг от спринта номер 4 трех месячной давности остался который уже ну никак нельзя не чинить.


Часть 4. «А если уволить бэкендера» — карта риска знаний

Самое опасное в айсберге — не код. Код лежит в репозитории, его видно. Опасно то, что живёт только в голове у человека:

  • Почему приняты те или иные решения (почему очередь в PostgreSQL, а не Redis; почему токены user и admin разделены);
  • Где закопаны грабли («вебхук Tinkoff надо ответить именно строкой “OK”, иначе он будет повторять»);
  • Как развернуть всё с нуля на новом сервере;
  • Где лежат секреты и как они ротируются;
  • Что делать, когда упало в 3 часа ночи.

Что снижает этот риск (и что стоит требовать от команды):

  1. Решения записаны. У нас для этого есть GSD pipeline с .planning/ с ADR (записи архитектурных решений — например, ADR об удалении Skill Tree) и RESEARCH-документы. Это не бюрократия — это «мозг проекта» вне головы одного человека.
  2. Окружение воспроизводимо. docker compose up поднимает весь проект одной командой. Новый человек стартует за часы, а не за недели.
  3. Тесты как документация. 62 файла тестов описывают, как система должна себя вести. Новый разработчик читает тесты — и понимает контракт.
  4. README, .env.example, Makefile. Точки входа, по которым можно собрать проект, не спрашивая автора.

Вопрос для оценки риска прямо сейчас. «Если ключевой разработчик уйдёт в отпуск на месяц без связи — что встанет?» Честный ответ на этот вопрос — и есть размер вашего bus-фактора. Всё, что в списке выше, работает на то, чтобы ответ был «ничего критичного».


Шпаргалка: вопросы, которыми продакт вскрывает «невидимую» работу в оценке

Когда тебе называют срок, который кажется подозрительно маленьким — пройдись по списку:

  1. «Это меняет структуру базы данных?» → если да, плюс миграция, плюс откат, плюс деплой миграции.
  2. «Это меняет API-контракт?» → если да, надо перегенерировать SDK и трогать обе стороны — фронт и бэк.
  3. «Какими тестами это покрыто?» → если ответ «никакими» — это не «сделано», это «сделано и непонятно, работает ли».
  4. «Что произойдёт, когда внешний сервис не ответит?» → проверка, что продумали несчастливый путь, а не только счастливый.
  5. «Это попадает в логи? Там нет персональных данных?» → особенно критично с требованием Zero PII.
  6. «Как мы узнаем, что это сломалось в проде?» → если ответ «нам напишет пользователь» — мониторинга нет.
  7. «Как мы это откатим, если пойдёт не так?» → если плана отката нет, риск выкатки недооценён.
  8. «Это новая интеграция или новый фоновый процесс?» → и то, и другое почти всегда дороже, чем кажется, из-за повторов, ошибок и порядка операций.

Если на половину вопросов ответ «об этом не подумали» — оценка занижена, и теперь у тебя есть конкретные пункты, чтобы это показать. Не «мне кажется, мало» — а «вот 4 слоя, которые в оценку не вошли».


Итог одной фразой

Приложение, которое видит пользователь, — это витрина. Всё остальное — контракты, база, миграции, очереди, логи, тесты, инфраструктура, секреты, обработка ошибок — это здание за витриной. Витрину можно поменять за полчаса. Здание за витриной — нельзя. И когда ты защищаешь бюджет, ты защищаешь не «лишнюю работу программистов» — ты защищаешь то, без чего витрина просто рухнет на третий день после открытия.

🙊🙉🙈