Транспорт бэкенда

REST API: архитектура и best practices

Рой Филдинг написал диссертацию в 2000 году, описав REST как архитектурный стиль для масштабируемых распределённых систем. Большинство API, называемых REST, нарушают минимум три из шести его ограничений - и при этом вполне успешно работают. Вопрос в том, какие нарушения критичны, а какие - разумные компромиссы.

  • **Stripe API** - эталон REST дизайна: ресурсное именование, Idempotency-Key для платежей, версионирование через URI (/v1/), детальные статус-коды
  • **GitHub REST API** - HATEOAS-like `_links` в ответах, pagination через Link header, версионирование через Accept header
  • **Twitter/X API v2** - переход от RPC-style v1 (`/statuses/show.json`) к ресурсному v2 (`/tweets/{id}`) как пример breaking migration
  • **PayPal API** - один из немногих публичных API с настоящим HATEOAS, `_links` управляют флоу платежа

6 ограничений REST

В 2000 году Рой Филдинг защитил диссертацию, где описал REST как архитектурный стиль для распределённых систем. Большинство API, называющих себя REST, нарушают минимум три из шести его ограничений.

REST расшифровывается как **Representational State Transfer**. Это не протокол и не стандарт - набор архитектурных ограничений. HTTP - де-факто реализация, но REST теоретически применим к любому протоколу.

  • **Uniform Interface** - единый интерфейс взаимодействия: ресурсы идентифицируются URI, манипуляции через представления, самоописательные сообщения
  • **Stateless** - каждый запрос содержит всю информацию для его обработки; сервер не хранит состояние клиента между запросами
  • **Cacheable** - ответы должны явно помечаться как кешируемые или нет (`Cache-Control`, `ETag`)
  • **Client-Server** - разделение ответственности: UI и хранение данных разделены, можно развивать независимо
  • **Layered System** - клиент не знает, общается ли с конечным сервером или прокси/балансировщиком
  • **Code on Demand** (опционально) - сервер может присылать исполняемый код (JavaScript)

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

**REST != HTTP.** REST - архитектурный стиль. HTTP - протокол. REST описывает как строить системы, HTTP - как передавать данные. Можно нарушать REST поверх HTTP (Stateful API) или соблюдать REST поверх других протоколов.

Клиент получает JWT token при входе и отправляет его в каждом запросе. Какое ограничение REST это реализует?

Дизайн ресурсов и HTTP методы

Самая частая ошибка - именовать эндпоинты как действия (`/getUser`, `/createOrder`, `/deleteItem`). В REST URI идентифицирует **ресурс**, а не действие. Действие передаётся HTTP методом.

МетодДействиеИдемпотентенБезопасенПример
GETПолучить ресурсДаДаGET /users/123
POSTСоздать ресурсНетНетPOST /orders
PUTЗаменить ресурс целикомДаНетPUT /users/123
PATCHОбновить частичноНет*НетPATCH /orders/456
DELETEУдалить ресурсДаНетDELETE /items/789

Статус-коды HTTP - часть языка REST. Клиент должен понимать результат операции без парсинга тела ответа.

КодЗначениеКогда использовать
200 OKУспех с теломGET, PUT, PATCH - ресурс возвращается
201 CreatedСозданPOST - новый ресурс создан, Location header
204 No ContentУспех без телаDELETE, PUT когда тело не нужно
400 Bad RequestОшибка клиентаНевалидный JSON, отсутствуют обязательные поля
401 UnauthorizedНет аутентификацииТокен отсутствует или истёк
403 ForbiddenНет правТокен валиден, но нет доступа к ресурсу
404 Not FoundРесурс не найденID не существует
409 ConflictКонфликт состоянияДублирующийся email, нарушение уникальности
422 UnprocessableСемантическая ошибкаJSON валиден, но данные некорректны
500 Internal ErrorОшибка сервераНепредвиденное исключение

**Антипаттерн:** возвращать `200 OK` с `{"success": false, "error": "Not found"}`. Это заставляет клиента парсить тело для определения успеха - нарушает смысл HTTP статус-кодов.

Идемпотентность и безопасность

Сеть ненадёжна: запрос может уйти, но ответ потеряться. Клиент не знает - выполнилась ли операция. Решение: повторить запрос. Но что если сервер выполнит его дважды?

Два независимых свойства операций: **идемпотентность** (повторный вызов даёт тот же результат) и **безопасность** (нет побочных эффектов).

МетодИдемпотентенБезопасенОбъяснение
GETДаДаЧтение данных - повторный GET возвращает то же
HEADДаДаКак GET, только без тела
OPTIONSДаДаВозвращает поддерживаемые методы
PUTДаНетPUT /users/1 с теми же данными - результат один
DELETEДаНетDELETE дважды: первый удаляет, второй - 404, состояние то же
POSTНетНетPOST /orders создаёт новый заказ каждый раз
PATCHНет*НетPATCH SET balance=100 - идемпотентен; PATCH balance+=10 - нет

**Stripe, Braintree, Adyen** - все платёжные системы реализуют Idempotency-Key. Клиент генерирует UUID перед запросом, передаёт в заголовке. Серверы хранят ответы 24-72 часа. Это стандартный паттерн для финансовых операций.

DELETE /users/42 вызван дважды. Первый вызов удалил пользователя, второй вернул 404. DELETE идемпотентен?

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

API изменяется - добавляются поля, меняются структуры, удаляются эндпоинты. Если клиенты (мобильные приложения, партнёры) не могут обновиться мгновенно, нужна стратегия сосуществования версий.

Ключевое разделение: **breaking changes** требуют новой версии, **non-breaking** - нет.

Breaking changeNon-breaking change
Удаление поля из ответаДобавление нового опционального поля
Переименование поляДобавление нового эндпоинта
Изменение типа поля (string → number)Добавление новых HTTP методов
Изменение семантики существующего поляРасширение enum новыми значениями (с осторожностью)
Изменение структуры URLДобавление необязательных query параметров

**Стратегия sunset:** при выпуске v2 не удалять v1 немедленно. Добавить заголовок `Sunset: Sat, 01 Jan 2026 00:00:00 GMT` в ответы v1 - клиенты могут логировать его и планировать миграцию.

В API v1 поле `user.name` возвращает строку. В новой версии нужно разделить на `firstName` и `lastName`. Это breaking change?

HATEOAS: гиперсвязи в ответах

HATEOAS - Hypermedia As The Engine Of Application State - последнее и самое редко реализуемое ограничение REST. Идея: ответ сервера содержит не только данные, но и ссылки на допустимые следующие действия.

На практике подавляющее большинство «REST API» - это RPC-over-HTTP: глаголы в URL или жёстко зашитые маршруты без гиперссылок. Fielding публично критиковал такие API, называя их «не REST».

**Когда HATEOAS реально нужен:** публичные API с длинным жизненным циклом (PayPal API использует HATEOAS), где клиенты - сторонние разработчики, которые не должны жёстко зашивать URL. Для внутренних API микросервисов HATEOAS обычно избыточен.

Любой API поверх HTTP с JSON - это REST API

REST - набор из 6 архитектурных ограничений. Большинство HTTP API нарушают хотя бы Stateless или Uniform Interface

Термин REST стал маркетинговым синонимом 'не SOAP'. Fielding в 2008 написал пост 'REST APIs must be hypertext-driven', где критикует подмену понятий

API возвращает заказ со ссылками `_links.pay` и `_links.cancel`. При статусе 'shipped' эти ссылки отсутствуют. Что это обеспечивает?

REST API: ключевые идеи

  • REST - 6 архитектурных ограничений Fielding 2000; Stateless критичен для горизонтального масштабирования
  • URI идентифицирует ресурс (существительное), HTTP метод - действие; статус-коды несут семантику без парсинга тела
  • Идемпотентность (GET/PUT/DELETE) позволяет безопасно ретраить запросы; Idempotency-Key решает проблему POST
  • HATEOAS - редко реализуемый, но концептуально важный принцип: сервер управляет допустимыми переходами состояния

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

REST - синхронный протокол взаимодействия, существующий в экосистеме других подходов к API дизайну:

  • GraphQL — Альтернатива REST: один эндпоинт, клиент задаёт структуру ответа
  • gRPC — Бинарный протокол поверх HTTP/2, альтернатива для микросервисов
  • HTTP Fundamentals — Транспортный уровень REST: методы, заголовки, статус-коды

Вопросы для размышления

  • Чем отличается идемпотентность от безопасности HTTP методов, и почему DELETE идемпотентен, хотя изменяет состояние?
  • При каких условиях HATEOAS добавляет реальную ценность, а при каких становится избыточным усложнением?
  • Как stateless ограничение REST влияет на выбор механизма аутентификации (сессии vs JWT)?

Связанные уроки

  • net-22-http-headers
REST API: архитектура и best practices

0

1

Войти

Какой HTTP метод корректно использовать для операции 'добавить товар в корзину', если корзина уже существует?