URL-дизайн на собеседовании системного аналитика

Проверь себя · 1/3разбор после ответа
Аналитик написал SELECT * FROM orders, users без WHERE и без JOIN ... ON. В orders 1000 строк, в users 500 строк. Что вернёт запрос?

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

REST-конвенции для URL

Главное правило REST: ресурсы — это существительные, а не глаголы. Действие над ресурсом выражается HTTP-методом, а не словом в пути. Один и тот же путь /orders/42 обслуживает и чтение, и обновление, и удаление — меняется только метод.

ХОРОШО:
GET    /orders          — список заказов
GET    /orders/42       — один заказ
POST   /orders          — создать заказ
PUT    /orders/42       — заменить заказ целиком
PATCH  /orders/42       — частично обновить
DELETE /orders/42       — удалить

ПЛОХО (глаголы в пути дублируют метод):
POST /createOrder
GET  /getOrderById?id=42
POST /orders/42/delete

Вложенные ресурсы показывают отношение «принадлежит». Заказ принадлежит пользователю, товарная позиция — заказу:

GET /orders/42/items    — позиции заказа 42
GET /users/99/orders    — заказы пользователя 99

Вложенность глубже двух-трёх уровней — антипаттерн: путь становится нечитаемым и хрупким. Если нужна связь между удалёнными сущностями, лучше вынести её в query-параметр или отдельный эндпоинт, чем городить /users/99/orders/42/items/7/reviews.

Множественное или единственное число

Стандарт индустрии — множественное число для коллекций: /orders, /users, /products. Это читается естественно и для списка (GET /orders), и для отдельного элемента (GET /orders/42).

Единственное число уместно только для синглтонов — ресурсов, которые существуют в единственном экземпляре в контексте запроса: /me, /profile, /settings. Здесь нет коллекции, поэтому и множественное число смысла не имеет.

На собесе главное — не смешивать стили. Если половина API на /orders, а половина на /order — это сразу читается как несогласованность. Консистентность важнее того, какую именно конвенцию вы выбрали.

Версионирование

Версия нужна, когда меняется контракт API так, что старые клиенты сломаются. Три подхода, и интервьюер ждёт, что вы назовёте плюсы и минусы каждого.

  • Версия в пути/v1/orders, /v2/orders. Самый распространённый и практичный вариант: версия видна сразу, легко роутить и кешировать, просто дебажить в логах и браузере. Минус — версионируется весь API целиком, а не отдельные ресурсы.
  • Версия в заголовкеAccept: application/vnd.api.v2+json. URL остаются «чистыми», можно версионировать точечно, но тестировать и отлаживать труднее: версию не видно в адресной строке, нужен инструмент вроде curl или Postman.
  • Версия в query-параметре/orders?version=2. Считается антипаттерном: query должен фильтровать ресурс, а не менять контракт; такие URL плохо кешируются и путают семантику.

Практичный дефолт для большинства сервисов — версия в пути. Именно этот ответ ждут на собесе, если вы обоснуете выбор простотой отладки и роутинга.

Query-параметры

Query-строка меняет представление одной и той же коллекции: фильтрует, сортирует, разбивает на страницы. Она не должна менять сам ресурс или его версию.

  • Фильтрация: ?status=paid&country=RU — отобрать подмножество по атрибутам.
  • Пагинация: ?limit=20&cursor=abc (cursor-based, устойчива к вставкам) или ?page=2 (offset-based, проще, но плывёт при изменении данных).
  • Сортировка: ?sort=created_at&order=desc — по какому полю и в каком направлении.
  • Sparse fieldsets: ?fields=id,name — вернуть только нужные поля, чтобы уменьшить размер ответа.
  • Включение связей: ?include=customer,items — подтянуть связанные сущности одним запросом вместо N+1.

Спецификация JSON:API задаёт готовые конвенции для всего этого — на неё удобно ссылаться на собесе, чтобы показать, что вы знаете стандарты, а не изобретаете формат с нуля.

Готовишься к собесу системного аналитика?
827 вопросов: REST, UML, OAuth, ERD, требования. Тренируйся в Telegram
Тренировать SA в Telegram

Slug'и

Slug — человекочитаемый идентификатор в URL вместо числового ID. Он лучше для SEO и понятнее пользователю:

/articles/how-to-write-clean-code
/products/iphone-15-pro

Генерация. Slug получают из заголовка: приводят к нижнему регистру, заменяют пробелы дефисами, транслитерируют кириллицу и выкидывают спецсимволы. «Как писать чистый код» → kak-pisat-chistyy-kod.

Стабильность. Смена slug ломает все внешние ссылки и SEO — поисковик теряет проиндексированный URL. Поэтому есть два подхода:

  • Сделать slug неизменяемым — он фиксируется при создании и больше не меняется, даже если заголовок отредактировали.
  • Разрешить менять, но настроить редиректы 301 со старого URL на новый, чтобы ссылки и позиции в поиске не потерялись.

На практике часто хранят и числовой ID (для внутренних связей и стабильности), и slug (для URL): /articles/1234-kak-pisat-chistyy-kod. Тогда роутинг работает по ID, а хвост-slug чисто для читаемости и его можно менять безболезненно.

Как это спрашивают на собесе

Типичный формат — открытая задача на проектирование. «Спроектируй REST API для корзины / заказов / отзывов». Интервьюер смотрит на несколько вещей:

  • Ресурс vs действие. Первое, что проверяют, — не появятся ли у вас /getOrders и /updateUser. Если появились, это минус.
  • Правильный метод под операцию. Ждут, что вы объясните разницу PUT (заменить целиком, идемпотентно) и PATCH (частичное обновление), почему DELETE не должен возвращать тело.
  • Обработка коллекций. Спросят, как отдавать список из миллиона заказов — здесь всплывают пагинация, фильтры и сортировка.
  • Версионирование и обратная совместимость. «Как добавить новое обязательное поле, не сломав старых клиентов?» Ответ — через версию или через необязательное поле с дефолтом.
  • Обоснование. Важнее не «единственно правильный» ответ, а то, что вы проговариваете trade-off'ы: почему cursor, а не offset; почему версия в пути, а не в заголовке.

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

Связанные темы

FAQ

Существительные или глаголы в URL?

Всегда существительные для ресурсов, действие задаётся HTTP-методом. POST /orders создаёт заказ, DELETE /orders/42 удаляет. Глаголы в пути (/createOrder) дублируют метод и считаются антипаттерном.

Как версионировать API?

Три способа: версия в пути (/v1/orders), в заголовке (Accept: ...v2+json) и в query (?version=2). Практичный дефолт — путь: версия видна, легко роутить и дебажить. Query-версионирование считается антипаттерном.

Множественное или единственное число в путях?

Множественное для коллекций (/orders, /users), единственное только для синглтонов (/me, /profile). Главное — не смешивать стили в рамках одного API.

Можно ли менять slug после публикации?

Технически можно, но это ломает внешние ссылки и SEO. Либо делают slug неизменяемым, либо настраивают 301-редирект со старого URL на новый. Часто в URL кладут и стабильный ID, и slug сразу.

Это официальная информация?

Нет. Статья основана на REST best practices, спецификации JSON:API и опыте прохождения собеседований. Конкретные конвенции зависят от команды и стандартов компании.


Тренируйте системный анализ — откройте тренажёр с 1500+ вопросами для собесов.