Перейти к основному содержимому

REST

Разработчику Архитектору Инженеру

REST

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

Что такое REST

REST (Representational State Transfer) — это архитектурный стиль проектирования сетевых приложений, предложенный Роем Филдингом в его докторской диссертации 2000 года. REST не является протоколом, стандартом или спецификацией. Это совокупность принципов и ограничений, которые задают правила взаимодействия между клиентом и сервером через гипермедиа.

REST опирается на существующие протоколы и технологии, в первую очередь на HTTP. Он использует их семантику максимально эффективно, превращая HTTP из простого транспортного механизма в полноценную платформу для построения масштабируемых, надёжных и легко понимаемых распределённых систем.

Основная идея REST — представление всего, с чем взаимодействует клиент, в виде ресурсов, доступных по уникальным URL. Каждый ресурс имеет своё состояние, которое можно получить, изменить, создать или удалить с помощью стандартных операций.


Принципы REST

REST базируется на шести ключевых ограничениях, которые вместе формируют его архитектурный стиль:

  1. Единообразие интерфейса (Uniform Interface)
    Все компоненты системы взаимодействуют через единый и предсказуемый интерфейс. Это достигается за счёт:

    • Идентификации ресурсов в запросах
    • Манипуляции ресурсами через представления
    • Самоописываемых сообщений
    • Гипермедиа как движущей силы состояния приложения (HATEOAS)
  2. Отсутствие состояния (Statelessness)
    Каждый запрос от клиента к серверу должен содержать всю необходимую информацию для его обработки. Сервер не хранит контекст предыдущих запросов. Это упрощает масштабирование, повышает надёжность и упрощает кэширование.

  3. Кэшируемость (Cacheability)
    Ответы сервера должны явно указывать, могут ли они быть закэшированы. Это позволяет клиентам и промежуточным узлам (например, CDN) эффективно использовать кэш, снижая нагрузку на сервер и ускоряя взаимодействие.

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

  5. Слоистая система (Layered System)
    Клиент не знает, взаимодействует ли он напрямую с сервером или через промежуточные слои (прокси, балансировщики, шлюзы). Это упрощает безопасность, масштабируемость и управление.

  6. Код по требованию (Code on Demand, опционально)
    Сервер может передавать исполняемый код (например, JavaScript), который клиент выполняет. Это единственный необязательный принцип REST.


Схема работы REST

Взаимодействие в REST-системе происходит по следующему циклу:

  1. Клиент отправляет HTTP-запрос к ресурсу по уникальному URL.
  2. Запрос содержит метод (GET, POST и т.д.), заголовки и, при необходимости, тело.
  3. Сервер обрабатывает запрос, применяя соответствующую логику.
  4. Сервер возвращает HTTP-ответ с кодом статуса, заголовками и, при необходимости, телом.
  5. Клиент интерпретирует ответ и, при необходимости, следует гиперссылкам из тела ответа (HATEOAS).

Этот цикл полностью stateless: каждый запрос независим, и сервер не хранит сессию клиента.


Инструменты для работы с REST

Postman

Postman — графический инструмент для тестирования и документирования REST API. Он позволяет:

  • Отправлять запросы с различными методами, заголовками и телом
  • Сохранять коллекции запросов
  • Автоматизировать тесты
  • Генерировать документацию
  • Имитировать серверные ответы (Mock Server)

Swagger (OpenAPI)

Swagger — спецификация для описания REST API в формате OpenAPI. Она позволяет:

  • Описывать структуру API в YAML или JSON
  • Генерировать интерактивную документацию
  • Создавать клиентские SDK автоматически
  • Валидировать запросы и ответы

curl

curl — командная утилита для отправки HTTP-запросов из терминала. Примеры:

# Получить список пользователей
curl -X GET https://api.example.com/users

# Создать нового пользователя
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name": "Ivan", "email": "ivan@example.com"}'

# Обновить пользователя
curl -X PUT https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"name": "Ivan Petrov"}'

REST-ресурс

REST-ресурс — это любая информация, которая может быть названа и доступна через URI. Ресурс представляет собой абстракцию реального объекта: пользователя, заказа, статьи, файла и т.д.

Каждый ресурс обладает тремя ключевыми свойствами:

  • Идентифицируемость — ресурс имеет уникальный URI, по которому его можно найти. Например: /users/123, /orders/456/items/789.
  • Модифицируемость — состояние ресурса можно изменить с помощью стандартных операций (POST, PUT, PATCH, DELETE).
  • Чётко определённое состояние — в любой момент времени ресурс находится в конкретном состоянии, которое можно получить через GET-запрос.

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


Statelessness

Statelessness означает, что сервер не сохраняет состояние взаимодействия с клиентом между запросами. Каждый запрос должен содержать всю необходимую информацию:

  • Аутентификационные данные (токен, API-ключ)
  • Параметры фильтрации, пагинации
  • Данные для создания или обновления ресурса

Преимущества statelessness:

  • Масштабируемость — любой сервер в кластере может обработать любой запрос
  • Надёжность — сбой одного сервера не влияет на другие
  • Простота — нет необходимости управлять сессиями
  • Кэшируемость — ответы можно кэшировать без учёта контекста

Если приложению требуется состояние (например, корзина покупок), оно должно храниться на клиенте (в cookies, localStorage) или в базе данных с привязкой к пользователю.


Доступные операции с веб-сервисами

REST использует стандартные HTTP-методы для выполнения CRUD-операций (Create, Read, Update, Delete):

Получение данных (Read)

GET /users
GET /users/123
  • Получает представление ресурса или коллекции
  • Не изменяет состояние сервера
  • Безопасный и идемпотентный

Создание новых ресурсов (Create)

POST /users
  • Создаёт новый ресурс в коллекции
  • Возвращает 201 Created с заголовком Location, указывающим на URI нового ресурса
  • Не идемпотентный: повторный вызов создаёт ещё один ресурс

Обновление ресурсов (Update)

Полное обновление:

PUT /users/123
  • Заменяет всё состояние ресурса
  • Идемпотентный: повторный вызов даёт тот же результат

Частичное обновление:

PATCH /users/123
  • Изменяет только указанные поля
  • Может быть неидемпотентным, если операция относительна (например, увеличение счётчика)

Удаление ресурсов (Delete)

DELETE /users/123
  • Удаляет ресурс
  • Идемпотентный: повторное удаление не вызывает ошибку

HTTP-методы

МетодИдемпотентностьБезопасностьНазначение
GETДаДаПолучение представления ресурса
POSTНетНетСоздание ресурса или выполнение действия
PUTДаНетПолное обновление ресурса
PATCHЗависитНетЧастичное обновление ресурса
DELETEДаНетУдаление ресурса
HEADДаДаПолучение метаданных без тела
OPTIONSДаДаПолучение поддерживаемых методов

Идемпотентность — свойство операции, при котором повторный вызов не изменяет результат после первого успешного выполнения.

Безопасность — свойство метода, при котором он не изменяет состояние сервера.


Как определить сервис, метод и HTTP-метод по URL

В правильно спроектированном REST API URL указывает только на ресурс, а не на действие. Метод действия определяется HTTP-глаголом.

Примеры:

  • /getUser?id=123 — глагол в URL, метод неочевиден

  • GET /users/123 — ресурс /users/123, метод GET

  • /createOrder — глагол в URL

  • POST /orders — ресурс /orders, метод POST

  • /updateUser?id=123 — глагол в URL

  • PUT /users/123 — ресурс /users/123, метод PUT

Правильный REST API читается как предложение: «Выполни [метод] над [ресурсом]».


Семантика HTTP

Статус-коды

Статус-коды — неотъемлемая часть контракта API:

  • 2xx — успех

    • 200 OK — успешный запрос
    • 201 Created — ресурс создан
    • 204 No Content — успешный запрос без тела ответа
  • 4xx — ошибка клиента

    • 400 Bad Request — невалидные данные
    • 401 Unauthorized — отсутствует аутентификация
    • 403 Forbidden — нет прав доступа
    • 404 Not Found — ресурс не найден
    • 409 Conflict — конфликт при создании/обновлении
    • 422 Unprocessable Entity — семантическая ошибка данных
  • 5xx — ошибка сервера

    • 500 Internal Server Error — необработанное исключение
    • 503 Service Unavailable — сервис временно недоступен

Никогда не возвращайте 200 при ошибке — это нарушает контракт.

Заголовки кэширования

  • Cache-Control — директивы кэширования (max-age, no-cache, public, private)
  • ETag — идентификатор версии ресурса для условных запросов
  • Last-Modified — дата последнего изменения

Пример условного запроса:

GET /users/123
If-None-Match: "abc123"

Если ресурс не изменился, сервер вернёт 304 Not Modified.

Идемпотентность

Как уже упоминалось, идемпотентность критична для надёжности:

  • GET, PUT, DELETE — идемпотентны
  • POST — не идемпотентен
  • PATCH — может быть идемпотентным, если операции абсолютны

Идемпотентные операции можно безопасно повторять при сетевых ошибках.


URL: правила формирования и составляющие

Общие правила

  • Используйте существительные, а не глаголы
  • Используйте множественное число для коллекций: /users, /orders
  • Стройте иерархию ресурсов: /users/123/orders/456
  • Используйте строчные буквы
  • Разделяйте слова дефисами (kebab-case) или без разделителей

Составляющие URL

https://api.example.com/v1/users/123/orders?status=completed&page=1
│ │ │ │ │ │ │
│ │ │ │ │ │ └── Query parameters
│ │ │ │ │ └── Resource ID
│ │ │ │ └── Sub-resource
│ │ │ └── Collection
│ │ └── Version
│ └── Host
└── Protocol

Шаблоны URL

ОперацияURLМетод
Список/usersGET
Создание/usersPOST
Получение/users/123GET
Обновление/users/123PUT/PATCH
Удаление/users/123DELETE
Подресурс/users/123/ordersGET/POST

Пагинация

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

Offset-based (классическая)

GET /orders?page=2&size=20

Ответ:

{
"data": [...],
"pagination": {
"page": 2,
"size": 20,
"total": 150,
"pages": 8
}
}

Cursor-based (для больших данных)

GET /orders?cursor=abc123&limit=20

Ответ:

{
"data": [...],
"next_cursor": "def456"
}

Cursor-based устойчива к изменениям в данных во время перелистывания.

Рекомендации

  • Устанавливайте max_size (например, 100)
  • Возвращайте 400 при невалидных параметрах
  • Документируйте тип пагинации

HATEOAS

HATEOAS (Hypermedia as the Engine of Application State) — принцип, согласно которому клиент переходит от одного состояния к другому, следуя гиперссылкам в ответах сервера.

Пример:

{
"id": 123,
"name": "Ivan",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders",
"update": "/users/123",
"delete": "/users/123"
}
}

Преимущества HATEOAS:

  • Клиент не зависит от жёстко закодированных URL
  • API становится самодокументируемым
  • Упрощается эволюция API

Недостатки:

  • Увеличивает объём ответов
  • Сложнее реализовать на клиенте

Resource-Oriented Design

Resource-Oriented Design (ROD) — подход к проектированию API, при котором всё строится вокруг ресурсов:

  1. Определите основные сущности домена
  2. Спроектируйте иерархию ресурсов
  3. Назначьте URI каждой сущности
  4. Определите допустимые операции для каждого ресурса
  5. Спроектируйте представления ресурсов

Пример иерархии для интернет-магазина:

/users
/users/{userId}
/users/{userId}/orders
/users/{userId}/orders/{orderId}
/users/{userId}/orders/{orderId}/items
/products
/products/{productId}

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

Версионирование необходимо с самого начала разработки API.

Способы версионирования

  1. В URL (рекомендуется)

    GET /api/v1/users
  2. В заголовке

    GET /api/users
    Accept: application/vnd.myapi.v1+json
  3. В параметре запроса (не рекомендуется)

    GET /api/users?version=1

Рекомендации

  • Используйте major-версии: v1, v2
  • Поддерживайте старые версии минимум 6–12 месяцев
  • Документируйте roadmap deprecation

Фильтрация, сортировка, поиск

Используйте query-параметры для управления представлением коллекций:

GET /users?role=admin&status=active&sort=name&order=desc&q=ivan
  • sort — поле для сортировки
  • order — направление (asc / desc)
  • q — глобальный поиск
  • остальные — фильтры по полям

Для сложных фильтров можно использовать специальные синтаксисы:

  • OData: $filter=age gt 18 and status eq 'active'
  • RSQL: age>18;status==active

Вложенные ресурсы

Для отношений «один ко многим» используйте вложенность:

GET /users/123/orders
POST /users/123/orders

Не дублируйте функциональность:

  • /orders — все заказы
  • /users/123/orders — заказы конкретного пользователя

Избегайте чрезмерной вложенности (более 3 уровней). Для глубоких связей используйте фильтрацию:

  • /companies/1/departments/2/employees/3/tasks/4
  • /tasks?employee_id=3

Частичное представление

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

GET /users?fields=id,name,email

Ответ:

[
{"id": 123, "name": "Ivan", "email": "ivan@example.com"}
]

Это особенно полезно для мобильных клиентов и медленных соединений.


Частичное представление (Field Selection)

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

Пример запроса:

GET /users?fields=id,name,email

Ответ:

[
{
"id": 123,
"name": "Ivan",
"email": "ivan@example.com"
}
]

Преимущества:

  • Экономия трафика для мобильных клиентов
  • Снижение нагрузки на сервер при сериализации
  • Упрощение логики клиента — получает только нужное

Реализация может быть как простой (разбор строки fields), так и сложной (вложенная выборка: fields=name,orders(id,status)).


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

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

Подходы к версионированию

  1. В URL (наиболее распространённый)

    GET /api/v1/users

    Преимущества: простота, видимость, поддержка прокси и CDN.
    Недостатки: нарушает принцип «ресурс один — URI один».

  2. В заголовке Accept

    GET /api/users
    Accept: application/vnd.myapi.v1+json

    Преимущества: чистый URI, соответствует REST.
    Недостатки: сложность отладки, неудобство в браузере.

  3. В параметре запроса (не рекомендуется)

    GET /api/users?version=1

    Недостатки: нарушает кэшируемость, усложняет маршрутизацию.

Рекомендации

  • Используйте major-версии: v1, v2
  • Не версионируйте мелкие изменения (добавление необязательных полей)
  • Поддерживайте старые версии минимум 6–12 месяцев
  • Документируйте roadmap deprecation
  • Возвращайте заголовок Deprecation: true для устаревших версий

Фильтрация, сортировка, поиск

Эти механизмы позволяют клиенту управлять представлением коллекций без создания отдельных эндпоинтов.

Фильтрация

Передаётся через query-параметры:

GET /users?role=admin&status=active

Для сложных фильтров можно использовать специализированные синтаксисы:

  • OData: $filter=age gt 18 and status eq 'active'
  • RSQL: age>18;status==active

Сортировка

Указывается через параметры sort и order:

GET /users?sort=name&order=desc

Можно поддерживать множественную сортировку:

GET /users?sort=role,name&order=asc,desc

Поиск

Глобальный поиск по нескольким полям:

GET /users?q=ivan

Для продвинутого поиска интегрируется Elasticsearch или аналоги.


Вложенные ресурсы

Вложенные ресурсы отражают иерархические отношения между сущностями.

Пример:

GET /users/123/orders        # Заказы пользователя 123
POST /users/123/orders # Создать заказ для пользователя 123
GET /orders/456/items # Позиции в заказе 456

Рекомендации

  • Используйте вложенность только для отношений «один ко многим»
  • Ограничьте глубину вложенности (максимум 3 уровня)
  • Для глубоких связей используйте фильтрацию:
    • /companies/1/departments/2/employees/3/tasks/4
    • /tasks?employee_id=3
  • Дублируйте функциональность на корневом уровне:
    • /orders — все заказы
    • /users/123/orders — заказы конкретного пользователя

HATEOAS

HATEOAS (Hypermedia as the Engine of Application State) — принцип, согласно которому клиент переходит от одного состояния к другому, следуя гиперссылкам, возвращаемым сервером.

Пример ответа:

{
"id": 123,
"name": "Ivan",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders",
"update": {
"href": "/users/123",
"method": "PUT"
},
"delete": {
"href": "/users/123",
"method": "DELETE"
}
}
}

Преимущества

  • Клиент не зависит от жёстко закодированных URL
  • API становится самодокументируемым
  • Упрощается эволюция API — можно менять структуру URI без поломки клиентов

Недостатки

  • Увеличивает объём ответов
  • Сложнее реализовать на клиенте
  • Требует дополнительной логики генерации ссылок

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


Resource-Oriented Design (ROD)

Resource-Oriented Design — методология проектирования API, ориентированная на ресурсы как основные строительные блоки.

Этапы проектирования

  1. Определите доменные сущности
    Пользователь, заказ, товар, категория.

  2. Спроектируйте иерархию ресурсов

    /users
    /users/{userId}
    /users/{userId}/orders
    /orders/{orderId}
    /orders/{orderId}/items
  3. Назначьте URI каждой сущности
    Используйте существительные во множественном числе.

  4. Определите допустимые операции
    Для каждого ресурса — набор разрешённых HTTP-методов.

  5. Спроектируйте представления
    Какие поля включать, как форматировать даты, как обрабатывать вложенные объекты.

ROD обеспечивает единообразие, предсказуемость и соответствие принципам REST.