← Все проекты уровня 11
Уровень 11 · Поисковые сервисыВариант A

Поиск товаров

Сервис каталога с полнотекстовым поиском, фасетами, фильтрами наличия и стабильной пагинацией.

Техническое задание

Данные и поиск

Создайте REST-сервис поиска по каталогу товаров. У товара есть устойчивый ID, артикул, название, описание, category_id, brand_id, атрибуты, цена в целых копейках RUB, остаток в штуках и время изменения UTC. Публикуется только товар со status=published; available=true означает stock_quantity > 0, а неизвестный остаток считается недоступным. Импорт и изменение записи должны валидировать обязательные поля, запрещать отрицательные цену и остаток; отсутствие остатка хранится отдельно от нуля. Поиск по названию, артикулу и описанию должен работать без учёта регистра и корректно обрабатывать Unicode, включая русские буквы и составные символы.

GET /api/v1/products/search?q=&category_id=&brand_id=&min_price_kopecks=&max_price_kopecks=&available=&sort=&cursor=&limit= возвращает карточки; limit по умолчанию 20, максимум 50, q до 120 символов. Цена включительна на обеих границах; min больше max даёт 400. Разрешены sort relevance, price_asc, price_desc, updated_desc, каждый с ID как последний ключ сортировки. Cursor использует keyset и подписывается сервером. GET /api/v1/products/facets принимает эти фильтры без cursor и limit. Каждая фасета применяет текст и фильтры, кроме собственного: фасета бренда игнорирует brand_id, фасета категории игнорирует category_id. Остальные применяются все. Фасеты сортируются count DESC, value ASC и ограничены 100 значениями. GET /api/v1/products/{id} возвращает карточку; PUT /api/v1/admin/products/{id} создаёт или заменяет товар; DELETE /api/v1/admin/products/{id} снимает публикацию. Локальный seed создаёт catalog_admin с токеном только в Authorization header; этот токен даёт доступ к admin-маршрутам. Используйте PostgreSQL, ручной SQL, миграции и GIN tsvector индекс по русской конфигурации. Запросы ищут все токены, регистр не учитывается, исправления опечаток нет.

Пустой q без фильтра даёт 400; с фильтром разрешён. Неизвестная категория даёт пустую выдачу. Неверный диапазон и неподписанный cursor дают 400; отсутствующий или снятый товар — 404. PUT и DELETE пишут товар и событие в outbox в одной транзакции. Индексатор применяет не более 200 событий в секунду, контрольная позиция сохраняется после применения. Повтор события идемпотентен по product ID и revision. Очередь ограничена 100 000; при заполнении изменение не подтверждается и возвращает 503. Поиск сообщает index_updated_at и lag_seconds. Приёмочный lag — до 60 секунд на 10 000 товарах. SQL deadline — 3 секунды. Ошибки базы не раскрываются клиенту. Все SQL-запросы используют context.Context с deadline 3 секунды. Запись товара и outbox version фиксируются атомарно. Индексатор читает события последовательно, применяет их повторяемо и подтверждает контрольную позицию после фиксации. При перезапуске он продолжает с контрольной позицией; удаление скрывает товар после обработки tombstone.

Структура программы

Веб-слой разбирает параметры и формирует ответы; предметный пакет задаёт правила запроса, фасет и сортировки; SQL-пакет хранит каталог и индекс, отдельный цикл применяет outbox. main.go только загружает конфигурацию, связывает зависимости и управляет запуском. Пакеты без циклов, общих utils и интерфейсов без потребителя. README показывает зависимости; тесты ядра без HTTP/БД, интеграционные отдельно.

Приёмка должна запускаться локально с PostgreSQL и детерминированными тестовыми данными, без сети и платных сервисов. Набор из 10 000 товаров включает русский текст, одинаковые оценки и цены, отсутствие атрибутов, товары без остатка и обновления с удалением. Тесты проверяют регистр и Unicode, AND-семантику выбранных фильтров, точные фасетные количества, сортировку с равными ключами, границы страниц, валидацию и восстановление индекса после перезапуска. Измерьте p95 поиска и индексации и опубликуйте команду, конфигурацию, размер и фактический результат, не выдавая его за общий SLA. Критерии готовности: unavailable товар не проходит available=true; стабильный каталог не даёт дублей между страницами; lag виден и не больше 60 секунд; недоступная БД не подтверждает изменение; миграции применяются с чистой базы.

Критерии готовности

Ожидаемый результат

Проверяемый результат

  • README показывает границы и направление зависимостей; main.go только связывает конфигурацию и запуск; модульные тесты ядра обходятся без HTTP и БД, интеграционные тесты отделены.
  • API предоставляет указанные маршруты поиска, фасетов, карточки и административного изменения товара.
  • Фильтры используют RUB в копейках; неизвестный остаток недоступен, а фасеты исключают только собственный фильтр.
  • Outbox обновляет индекс повторяемо, удаление становится видимым после контрольной позиции, ответ показывает лаг.
  • Набор 10 000 товаров имеет воспроизводимую команду замера p95 и описанную конфигурацию.
  • Офлайн-тесты проверяют Unicode, доступность, количества фасетов, стабильную пагинацию, валидацию и перезапуск.
  • Ошибки PostgreSQL скрыты от клиента; SQL timeout и чистые миграции описаны.
  • README позволяет поднять PostgreSQL и сервис локально без доступа в сеть.
  • Отчёт фиксирует версию данных и конфигурацию так, чтобы замер p95 можно было повторить.
  • Отчёт указывает точную версию набора и конфигурацию среды, поэтому p95 можно сравнить после изменений.