REST

REST — архитектурный стиль для распределённых систем. В HTTP API его основные идеи: ресурс имеет URI, клиент передаёт представление ресурса, запрос самодостаточен, а стандартная семантика методов и кодов ответа остаётся видимой.

Ресурс и представление

GET /orders/42 HTTP/1.1
Host: api.example.test
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "v7"
 
{"id":42,"status":"PAID"}

/orders/42 обозначает ресурс, JSON — одно из его представлений. URI лучше строить из существительных; действие выражает HTTP method или отдельный доменный ресурс:

POST /orders/42/cancellations

Это часто яснее, чем POST /orders/42/cancel, если отмена имеет собственное состояние и аудит.

Семантика методов

МетодНазначениеSafeIdempotent
GETполучить представлениедада
HEADполучить headers без bodyдада
POSTсоздать подчинённый ресурс или запустить обработкунетнет по умолчанию
PUTполностью заменить состояние по известному URIнетда
PATCHчастично изменить ресурснетне гарантируется
DELETEудалить ресурснетда по семантике результата

Идемпотентность означает одинаковое целевое состояние после повторов, а не одинаковый status code или отсутствие журналирования.

Коды ответа

  • 200 OK — операция успешна, есть представление результата.
  • 201 Created — ресурс создан; полезен Location.
  • 202 Accepted — запрос принят, обработка ещё не завершена.
  • 204 No Content — успех без body.
  • 400 Bad Request — синтаксис или общая валидация запроса.
  • 401 Unauthorized — аутентификация отсутствует/неуспешна.
  • 403 Forbidden — личность известна, доступа нет.
  • 404 Not Found — ресурс не найден или скрыт политикой доступа.
  • 409 Conflict — конфликт текущего состояния, например версия устарела.
  • 412 Precondition Failed — не выполнено If-Match.
  • 422 Unprocessable Content — формат понятен, доменные поля недопустимы.
  • 429 Too Many Requests — ограничение частоты.
  • 500/502/503/504 — ошибки сервера или downstream.

Конкурентное изменение

PUT /orders/42 HTTP/1.1
If-Match: "v7"
Content-Type: application/json
 
{"id":42,"status":"CANCELLED"}

Сервер применяет изменение, только если ETag всё ещё "v7". Иначе отвечает 412, не затирая чужое обновление.

Stateless не означает “без состояния”

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

Pagination и сетевой N+1

GET /orders?limit=50&cursor=next-token

Если клиент получает список, а затем делает отдельный запрос для каждой строки, локальный N+1 превращается в сетевой. В рабочем случае я заранее загрузил доступные набором данные и дедуплицировал оставшиеся одиночные обращения. См. Кейс сетевой N plus 1.

Типичные сбои

  • Возвращать 200 с текстом ошибки и ломать стандартную обработку клиента.
  • Использовать GET для изменения состояния: кэш, crawler или retry могут повторить его.
  • Считать любой PATCH идемпотентным.
  • Возвращать entity ORM напрямую и случайно раскрывать поля или запускать lazy loading.
  • Делать бесконечную offset-pagination на изменяемом наборе без стабильной сортировки.
  • Менять контракт ответа без обратной совместимости.

Вопросы для собеседования

Чем resource отличается от representation? Resource — логическая сущность по URI, representation — её передаваемая форма.

Почему DELETE идемпотентен, даже если второй ответ 404? Повтор не меняет целевое состояние: ресурс остаётся удалённым.

Когда вернуть 202? Когда обработка асинхронна; клиенту нужен способ узнать её статус.

Как защититься от lost update? Версионирование, ETag и условные запросы If-Match.

REST требует JSON? Нет. Формат representation выбирается отдельно через media type.

См. Веб-сервисы, Веб протоколы, Отличия POST и PUT и PATCH методов HTTP.