Вы просили архитектуру без overengineering и разбивку на этапы. Здесь и то, и другое: схема, модель данных, эндпоинты, пять этапов с ценой и сроком — и отдельно места, где такие MVP ломаются.
Для MVP с таким списком функций микросервисы не нужны: один сервис NestJS, одна база, отдельное хранилище медиа. Разделение по модулям внутри — чтобы потом можно было вынести то, что реально упрётся в нагрузку, а не всё сразу.
Всё это разворачивается в вашем аккаунте: база, storage и сервер — под вашим контролем с первого дня, как вы и написали. Я работаю в вашей инфраструктуре, а не в своей, чтобы передача доступов не была отдельным событием в конце.
Ваши «нестандартные бизнес-правила» — главный аргумент. Правила, которые не ложатся в стандартную схему, в реляционной базе с обычным кодом пишутся как обычный код. В managed-конструкторе они превращаются в обход платформы.
Быстрее на старте, дороже на выходе: лента по подпискам и поиск пользователей в документной базе делаются неудобно, а перенос данных потом — отдельный проект. Плюс ваше требование «всё под контролем владельца» с ним выполняется наполовину.
Если приоритет — скорость и экономия, это тот самый вариант: PostgreSQL остаётся настоящим, авторизация и storage уже готовы, а RLS закрывает доступ на уровне базы. Готов собрать MVP на нём — скажу честно, где он ограничит.
Очереди, кэш, отдельный сервис ленты, GraphQL — на MVP это расходы без отдачи. Добавляется тогда, когда появятся цифры, а не заранее «на вырост».
users id · apple_sub · google_sub · username(UNIQUE, citext) · display_name
· avatar_key · bio · created_at · is_deleted
follows follower_id · followee_id · created_at PK(follower_id, followee_id)
posts id · author_id · caption · media_key · media_type · width · height
· duration_ms · created_at · is_deleted
likes user_id · post_id · created_at PK(user_id, post_id)
comments id · post_id · author_id · body · created_at · is_deleted
blocks blocker_id · blocked_id · created_at PK(blocker_id, blocked_id)
reports id · reporter_id · target_type · target_id · reason · created_at · status
индексы, которые решают:
follows(followee_id, follower_id) — кто на меня подписан
posts(author_id, created_at DESC) — лента и профиль
posts(created_at DESC, id DESC) — keyset-пагинация
likes(post_id) — счётчик реакций
users(username) — UNIQUE, поиск по префиксу
Самая частая ошибка в ленте. При новых публикациях записи сдвигаются, и пользователь видит дубли или пропуски при прокрутке. Нужен keyset по паре (created_at, id) — он же не деградирует на больших объёмах.
Проверка «занят ли ник» отдельным запросом перед вставкой — гонка: двое получают один ник. Решается UNIQUE-ограничением в базе и обработкой конфликта, а не проверкой в коде. Плюс citext, иначе Anna и anna — разные люди.
Если видео идёт телом запроса через API, сервер упирается в память и таймауты на первых же пользователях. Файл должен идти в storage напрямую по presigned URL, а бэкенд получать только ключ объекта.
Блокировка реализована в профиле, но забыта в ленте, комментариях и поиске. Пользователь блокирует — и продолжает видеть человека. Фильтр должен применяться во всех выдачах, и это проверяется тестом, а не на глаз.
COUNT на каждую карточку ленты — N+1 запросов. На MVP хватает денормализованного счётчика в posts, обновляемого в той же транзакции.
Эндпоинт жалоб есть, а смотреть их негде. Нужен хотя бы минимальный список для владельца — иначе модерация существует только на бумаге, и это всплывает при ревью в App Store.
Каждый этап заканчивается работающей частью, которую можно потрогать из приложения, а не «процентом готовности». Оплата поэтапно через Сейф.
| Этап | Что внутри | Результат | Дней | Часов | Стоимость |
|---|---|---|---|---|---|
| 1. Каркас и авторизация | Проект, база, миграции, Apple и Google OAuth, JWT + refresh, профиль, уникальный username | приложение логинится и заводит профиль | 7 | 38 | 18 000 грн |
| 2. Соцграф | Подписки, подписчики, поиск пользователей, блокировки | пользователи находят и подписываются | 5 | 28 | 13 000 грн |
| 3. Публикации и медиа | Presigned-загрузка в storage, фото и короткое видео, метаданные, удаление | публикация из приложения работает | 7 | 38 | 18 000 грн |
| 4. Лента и реакции | Хронологическая лента по подпискам, keyset-пагинация, лайки, комментарии, счётчики | рабочая лента | 6 | 32 | 15 000 грн |
| 5. Модерация и сдача | Жалобы и их разбор, защита API, логирование, OpenAPI, деплой, передача доступов | боевой контур у вас | 5 | 28 | 16 000 грн |
| Итого MVP, фиксированная стоимость | 30 | 164 | 80 000 грн | ||
Этапы идут последовательно и оплачиваются по факту сдачи каждого. Остановиться можно после любого — то, что сдано, остаётся у вас в Git и работает.