Как стать автором
Обновить

Проектирование REST API: спорные вопросы с проектов и собеседований на системного аналитика (и не только)

Уровень сложностиСредний
Время на прочтение13 мин
Количество просмотров36K

Привет! Эта статья на холиварную тему. Комментарии приветствуются, особенно если у вас есть интересный опыт, которым можете поделиться!

Проектирование REST API - это процесс создания дизайна методов обмена данными. Дизайн - это субъективное. У одних "так", у других "сяк". А кто прав? Иногда все, а иногда нет.

В этой статье Системные аналитики, Backend-разработчики и Архитекторы найдут спорные вопросы с проектов и собеседований, которые связаны с созданием контрактов REST API.

Вопросы взяла по итогам общения с системными аналитиками, которые недавно проходили собеседования, с моего открытого вебинара, и из комментариев к серии постов про REST API в моем канале, где я рассказываю про него и показываю, как использовать на практике.

-------------------------------------

Спорные вопросы:

  1. Можно ли использовать метод POST для получения данных?

  2. Можно ли сделать в проекте все методы POST?

  3. Можно ли в GET передавать тело запроса?

  4. Как правильно именовать эндпоинты - ед. число или мн. число (/user или /users)?

  5. Как правильно строить URL - нужно ли писать create/update в названии метода?

  6. Какой код ответа на метод POST: 200 или 201?

  7. Что вернуть в ответ, если получен пустой результат - пустой массив или 404?

-------------------------------------

REST API - что это и для чего?

REST API - это архитектурный стиль, который определяет, по каким правилам приложения должны обмениваться данными между собой. Он используется для создания веб-сервисов, мобильных приложений, интеграционных платформ и других IT-решений.

Впервые был определен и описан Роем Филдингом в диссертации 2000-го года. Рой Филдинг - соавтор спецификаций HTTP и URI.

Главная цель REST API - облегчить передачу и управление информацией между разными системами: создавать, читать, изменять, удалять (CRUD). REST API использует для этого стандартные HTTP-запросы: GET, POST, PUT, DELETE и другие.

🔹 REST API - это архитектурный стиль проектирования взаимодействия приложений.
🔹 HTTP - протокол, на основе которого работает REST API.

Отличие REST API от других видов API, таких как SOAP API, ftp и RPC, заключается в том, что REST API не имеет жестких правил и структур, и может быть использован с любым языком программирования - Java, Python, C++ и другие.

Из-за того, что REST API не имеет жестких правил и структур, и возникают спорные вопросы, связанные с его проектированием.

Всё самое важное про REST API в одной картинке. Уже по ней у меня есть спорный вопрос: "Можно ли использовать метод POST для получения данных?".
Всё самое важное про REST API в одной картинке. Уже по ней у меня есть спорный вопрос: "Можно ли использовать метод POST для получения данных?".

1. Можно ли использовать метод POST для получения данных?

Да, можно.

Метод POST изначально предназначен для отправки данных на сервер с целью их обработки и создания новых записей в БД. В то же время его можно использовать для получение данных в следующих случаях:

1. Запросы на получение данных (списки объектов) с большим количеством фильтров

Когда необходимо реализовать большое количество фильтров для получения списка, то решение отправлять их все в URL запроса как query-параметры не лучшее, т.к. это делает URL очень длинным. Это может вызвать проблемы с ограничениями на длину URL в некоторых веб-серверах или браузерах.

Кроме того, сложные и многочисленные параметры в URL могут затруднить читаемость и понимание запроса для других разработчиков и пользователей API.

Пример:

Допустим, у нас есть API для получения списка книг из базы данных. Пользователь может фильтровать этот список по автору, жанру, году публикации, издательству, рейтингу и многим другим параметрам. Если пытаться передать все эти фильтры в URL, то он может выглядеть так:

Метод "Получение списка книг с применением фильтров":

GET https://test-system.com/api/books?author=Толстой&genre=роман&year=1877&publisher=Огонек&rating=5&...

Такой подход делает URL громоздким и может привести к превышению ограничения на длину URL, особенно если захочется добавить еще больше фильтров.

Вместо этого, можно использовать метод POST для передачи всех этих параметров в теле запроса в формате JSON:

Метод "Создание поискового запроса на получение списка книг с применением фильтров":
POST https://test-system.com/api/books/search 

{
    "author": "Толстой",
    "genre": "роман",
    "year": 1877,
    "publisher": "Огонек",
    "rating": 5,
    ...
}

Такой подход делает запрос более структурированным и читаемым, избегая проблем с длиной URL. Кроме того, это позволяет легко расширять список фильтров в будущем без страха ограничений.

В двух этих примерах результат будет идентичный: тело ответа со списком книг, отфильтрованных по заданным пользователем параметрам.

Стоит отметить, что при выборе метода POST для таких запросов, необходимо четко документировать это поведение в API-документации, чтобы избежать путаницы среди пользователей API.

2. Асинхронные запросы на получение данных: комбинирование POST и GET

Асинхронные запросы на получение данных реализуются за счет комбинации методов POST и GET.

Асинхронные запросы часто используются в ситуациях, когда необходимо обработать большой объем данных или когда процесс может занять длительное время. Это могут быть запросы на формирование отчетов или сложные поисковые запросы в системах-агрегаторах.

Реализуются асинхронные запросы последовательным использованием методов:

  • POST - используется для создания задачи на сервере. При получении такого запроса, сервер помещает задачу в очередь на обработку и возвращает идентификатор задачи.

  • GET - с помощью этого метода можно проверить статус выполнения задачи, используя идентификатор, предоставленный на предыдущем шаге. Как только задача завершена, через этот же метод можно получить результаты его выполнения.

Отчеты: В больших системах формирование отчетов может требовать обработки огромных объемов данных, что занимает много времени. Вместо ожидания окончания процесса, пользователь может отправить запрос на формирование отчета и вернуться к нему позже.

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

Метод размещения запроса на формирование отчета:

POST /api/reports/sales

{
    "startDate": "2023-01-01",
    "endDate": "2023-12-31"
}

После обработки этого запроса сервер создает задачу на формирование отчета и помещает ее в очередь.

Cервер возвращает идентификатор задачи. Этот уникальный идентификатор позволяет пользователю отслеживать статус формирования отчета.

{
    "taskId": "12345"
}

2. Через некоторое время пользователь может отправить GET-запрос, чтобы проверить, готов ли отчет или получить его, если он готов.

Метод получения информации по статусу формирования отчета или готового отчета:

GET /api/reports/sales/status/{taskId}

Если отчет еще формируется:

{
    "status": "progress"
}

Если отчет уже готов, пользователь может скачать его, используя предоставленный URL в ответе или получить его структуру. Зависит от выбранного способа реализации.

Пример, если в ответ на отчет готова ссылка на его загрузку:

{
    "status": "completed",
    "reportUrl": "/api/reports/sales/download/12345"
}

Сложные поисковые запросы: Системы-агрегаторы, такие как поисковики авиабилетов, иногда ищут информацию во внешних системах - поставщиках данных. Такой запрос может занять много времени, особенно если один или несколько внешних источников медленно отвечают на запросы.

Представим, что вы создаете API для системы бронирования авиабилетов. Пользователь хочет найти билеты на определенную дату.

1. Пользователь отправляет POST-запрос с деталями поиска (дата, пункт назначения и т.д.).

Метод размещения запроса на поиск авиабилетов:

POST /api/tickets/search

{
    "departure": "2023-10-30",
    "destination": "Rome",
    ...
}

Сервер возвращает идентификатор задачи - идентификатор созданного запроса на поиск.

{
    "searchId": "Уникальный идентификатор задачи"
}

2. Через некоторое время пользователь делает GET-запрос с этим идентификатором, чтобы узнать статус поиска.

Метод получения статуса или информации по результатам поиска, если он завершен:

GET /api/tickets/search/{searchId}
или
GET /api/tickets/search/status/{searchId}

Ответ (если поиск еще не завершен):

{
    "status": "progress"
}

Как только поиск завершен, пользователь получает список доступных рейсов в ответ на GET-запрос.

Этот подход обеспечивает эффективное использование ресурсов сервера и хороший UX для пользователя, который не вынужден ждать завершения длительных операций, а может поставить задачу на получение данных в фон, или получать результаты поиска быстро и порциями, по мере сбора данных из разных систем (помните, когда во время поиска авиабилетов у вас постоянно добавляются новые и новые записи?).

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

2. Можно ли сделать в проекте все методы POST?

Можно, но не рекомендуется :)

Использование только методов POST в API — это не самое красивое решение, которое может вызывать вопросы. Однако понимание возможных причин и последствий такого подхода, может помочь принять, понять и простить.

Почему так могут делать:

  • Универсальность: в теле запроса POST можно передавать большие объемы данных, что может быть удобно для сложных запросов, в том числе поисковых.

  • Развитие действующего API: потому что так уже сделаны все методы API и сейчас разработчики продолжают поддерживать дизайн API в том же стиле.

  • Другие причины: будет интересно почитать в комментариях.

Возможные проблемы такого подхода:

  1. Несоблюдение стандартов: Принципы REST подразумевают использование различных HTTP-методов для разных CRUD-операций (GET для получения, PUT для обновления и т.д.). Использование только POST может запутать разработчиков и усложнить интеграцию с вашей системой.

  2. Сложности кэширования: HTTP-кэширование обычно применяется к GET-запросам. Не критично.

  3. Отсутствие идемпотентности: Повторное выполнение одного и того же POST-запроса может привести к разным результатам. Не критично.

Примеры API-документации, где только POST-запросы:

В открытом доступе я знаю только про DaData. Хороший сервис, но почему-то все запросы реализованы через POST. Верю, что этому есть какое-то объяснение

API: организация по ИНН или ОГРН:

API: геокодирование (координаты по адресу):

3. Можно ли в GET передавать тело запроса?

Не стоит, оно будет проигнорировано или обрезано.

HTTP-спецификация не запрещает передачу тела запроса в методе GET, но этот подход считается нестандартным и может вызвать определенные проблемы и вопросы при разработке и использовании API.

Почему передача тела запроса в GET может показаться привлекательной? В некоторых сценариях может возникнуть потребность в передаче сложных данных для фильтрации или поиска, которые сложно или невозможно передать через URL в виде query-параметров.

Главная проблема такого подхода: не все клиенты и серверы поддерживают передачу тела запроса в методе GET. Некоторые из них могут игнорировать тело запроса или возвращать ошибку. Проще говоря, тело запроса будет проигнорировано или "обрезано".

Примеры исключений из правил, когда можно передавать тело в GET, привести не могу. Не встречала таких решений.

4. Как правильно именовать эндпоинты - ед. число или мн. число (/user или /users)?

Можно и так, и так. Но нужно ориентироваться на то, как названы остальные эндпоинты в проекте. В новом API я бы делала в ед. числе, т.к. у меня больше такого опыта, чем с мн.ч.

В именовании ресурсов в REST API есть разные подходы, и, честно говоря, нет строгого стандарта.

Однако на практике чаще всего предпочитают использовать единственное число для ресурсов. Но и множественное число также активно используется!

Согласно множественному подходу, даже если вы хотите получить информацию о конкретной задаче, вы все равно обращаетесь к "коллекции" задач и выбираете одну из них.

Примеры:

Единственное число:
GET /task/123 - получить информацию о задаче с ID 123.
POST /task - создать новую задачу.

Множественное число:
GET /tasks - получить список всех задач
GET /tasks/123 - получить информацию о задаче с ID 123.
POST /tasks - создать новую задачу.

Множественное число представляет собой коллекцию элементов, что соответствует идеологии REST для метода добавления нового элемента в коллекцию и чтения списка - /tasks читается легче, где у вас есть коллекция ресурсов и конкретные ресурсы внутри этой коллекции.

Рекомендации по множественному числу есть в гайдлайнах:

1) Microsoft API Guidelines: 7.4.1. POST ---> POST http://api.contoso.com/account1/servers

2) Google Cloud API Design Guide: Resource names ---> Example 1: A storage service has a collection of buckets, where each bucket has a collection of objects

При этом, если мы взглянем на Яндекс:

Пример с ед. числом:
https://yandex.ru/dev/rasp/doc/ru/reference/nearest-settlement


Пример с мн. числом:
https://yandex.ru/dev/market/partner-api/doc/ru/overview/express

Наглядный пример как в рамках одной огромной компании работали разные команды.

От чего зависит выбор? От опыта команды, и как комфортнее воспринимать методы при чтении.

Помимо этих источников, хорошей практикой также является изучение и анализ популярных публичных API, чтобы увидеть общепринятые стандарты в сообществе. А лучшая практика - посмотрите как у вас уже реализован API в компании!

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

Хотя я предпочитаю единственное число, для меня важнее договоренность с командой и поддержка единого подхода на протяжении всего проекта.

5. Как правильно строить URL - нужно ли писать create/update в названии метода?

Не рекомендуется так делать. Это считается плохим тоном.

В принципах RESTful дизайна, использование глаголов, таких как create или update, в URL считается плохой практикой. Идея REST заключается в
использовании стандартных HTTP-методов (GET, POST, PUT, DELETE и т. д.)
в сочетании с номинативными URL, представляющими ресурсы.

  • GET - получить,

  • POST - создать,

  • PUT - изменить или создать,

  • PATCH - изменить,

  • DELETE - удалить.

Типы методов и есть глаголы, дублирование глаголов create и update в URL избыточно. Не рекомендуется их дублировать в URL! Зачем? API и так прекрасно читается, если он спроектирован по стандартам RESTful и в нём не все POST!

Примеры:

Неправильно:

  • POST /products/create (использует глагол create)

  • PATCH /products/update/123 (использует глагол update)

Правильно:

  • POST /products (для создания нового продукта)

  • PATCH /products/123 (для обновления продукта с идентификатором 123)

В целом, при разработке RESTful API рекомендуется избегать включения глаголов, описывающих действия, в URL.

Дополнительно по примерам формирования URL
Дополнительно по примерам формирования URL

6. Какой код ответа на метод POST: 200 или 201?

При правильном подходе к использованию метода POST для создания новых данных в БД рекомендуется на успешные операции отвечать HTTP-201 Created. Хотя HTTP-200 ОК тоже допустим, когда в результате выполнения запроса новые данные не создаются.

Выбор правильного кода ответа так же важен, как и структура запросов и ответов. Ответы HTTP имеют стандартизированные коды, которые передают определенный смысл клиентам API.

Метод POST обычно используется для создания нового ресурса - новых данных в БД. После успешного создания нового ресурса, наиболее подходящим кодом ответа является 201 Created. Он явно указывает на то, что запрос был успешно выполнен и в результате был создан новый ресурс.

200 OK также является допустимым кодом ответа для метода POST. Он обычно используется в ситуациях, когда POST-запрос не приводит к созданию нового ресурса, но успешно обработан. Такие ситуации могут включать в себя операции, которые вызывают некоторые действия на сервере или просто передают данные без их сохранения в базе данных.

Пример:

Метод для создания новых пользователей:

POST /users

{
    "name": "Иван",
    "email": "ivan@example.com"
}

Рекомендуемый ответ:

  • HTTP-код: 201 Created

  • Тело ответа: может включать информацию о новом пользователе или быть пустым.

При использовании метода POST для создания новых ресурсов в вашем RESTful API рекомендуется ответ с кодом 201 Created. Это не только соответствует стандартам HTTP, но и ясно передает намерение сервера клиенту или пользователю.

В любом случае, зафиксируйте в API-гайдлайнах вашей компании какой код ответа вы будете возвращать на POST в случае, если запрос не приводит к созданию данных. Это важно для развития API, чтобы вся команда, которая работает над одним проектом понимала внутренние правила проектирования.

7. Что вернуть в ответ, если получен пустой результат - пустой массив или 404?

Оба варианты допустимы и правильные. Зафиксируйте то, какой результат вы будете возвращать в случае пустых результатов в корпоративном REST API гайдлайне, чтобы команда разработки знала однозначный ответ на этот вопрос при развитии API. Клиенты API также должны быть проинформированы о том, какие результаты в этих случаях ожидать, и одинаково обрабатывать их.

Одной из основных дилемм при проектировании API является вопрос о том, как правильно ответить на запрос, когда искомые данные отсутствуют. Это актуально для ситуаций, когда клиент запрашивает список, но объекты данных в нем отсутствуют.

Вариант 1 - пустой массив:

Если клиент запрашивает список элементов, то наиболее логичным было бы вернуть пустой список (в формате JSON это будет пустой массив []), когда элементы отсутствуют. Это четко указывает на то, что запрос был корректным, и в текущий момент элементы отсутствуют.

Пустой массив позволяет клиентским приложениям без дополнительных проверок обрабатывать ответ, будь то пустой или полный список.

Вариант 2 - ошибка с кодом HTTP-404 Not Found: Код 404 Not Found явно указывает на то, что искомый ресурс отсутствует. Однако, при запросе списка ресурсов, это может вызвать путаницу, потому что 404 чаще ассоциируется с ситуацией, когда сам endpoint не найден (Помните, когда на сайтах страница не найдена, то пишут 404 Not Found?).

В то время как для списка ресурсов лучше возвращать пустой массив, для конкретного ресурса (например, конкретный пользователь или статья) 404 Not Found подходит, если он не существует.

Примеры:

Метод получения списка пользователей:
GET /users
Рекомендуемый ответ, если пользователи не найдены:
HTTP-код:
200 OK
Body ответа: []

Метод получения информации о пользователе с id=123:
GET /users/123
Рекомендуемый ответ, если пользователь не найден:
HTTP-код:
404 Not Found
Body ответа: нет.

При разработке RESTful API я бы рекомендовала везде возвращать 200 и пустой массив в случае отсутствия результатов по запросу, а не код ошибки 404 Not Found. А для получения информации об одном конкретном объекте как раз 404 Not Found.

Можно делать в обоих случаях 200 и [], можно делать в обоих случаях 404 ошибку, но моя настоятельная рекомендация: внесите рекомендации по стандарту ответа на такие ситуации в корпоративный гайдлайн по дизайну REST API и проинформируйте о принятом решении по обработке такого исключения клиентов вашего API.

Заключение

В статье нет ни единого слова про НУЖНО. Здесь я рассказала про негласные стандарты отрасли IT и свой опыт проектирования и тестирования REST API разных проектов.

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

При развитии REST API придерживайтесь тех правил, которые уже ранее были приняты в проекте и поддерживайте их. Если эти правила не зафиксированы - разработайте корпоративный гайдлайн по дизайну REST API. Т.е. если в API уже все POST, не надо умничать и добавлять GET. Это будет выбиваться из общей концепции и вызывать вопросы.

При создании нового REST API сразу вводите корпоративный гайдлайн по дизайну REST API и в нём фиксируйте:

  • общие правила по дизайну методов:

    • типы методов;

    • дизайн URL эндпоинтов;

    • коды ответов;

    • реакция на типовые ошибки;

    • и другие.

  • обоснование принятых решений - почему команда приняла решение делать так, а не иначе.

Документация и гайдлайны в проекте наше всё! Особенно в тех вопросах, где дело касается дизайна (хоть и программного интерфейса) и опыта команды.

Хорошие дизайнеры UI/UX делают гайдлайны для приложений, чтобы можно было развивать их и пользователи интуитивно понимали и чувствовали, что приложение сделано в одном визуальном стиле. В разработке API это тоже применимо!

Что еще почитать и посмотреть по REST API

Хорошо подойдет для тех, кто только пытается разобраться что это и зачем.

YouTube:

База знаний GetAnalyst:

Книги:

P.S.

Какие еще спорные вопросы вы встречали по дизайну REST и какие решения по ним? Пишите в комментарии.

P.P.S.

Делитесь, когда у вас в компании принято использовать 400-е ошибки (ошибки клиента), а когда 500-е (ошибки сервера)?

Например, что вернете, если при создании нового города в справочнике обнаружили, что город с таким названием уже ранее был создан в БД (найден дубликат)?

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 25: ↑21 и ↓4+20
Комментарии302

Публикации

Истории

Работа

Ближайшие события

Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
OTUS CONF: GameDev
Дата30 мая
Время19:00 – 20:30
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область