REST: ресурсы, представления, stateless
Урок разбирает, что такое REST, чем ресурс отличается от представления и почему серверу выгодно не помнить клиента между запросами.
REST (Representational State Transfer) — это архитектурный стиль для веб-API, в котором всё описывается как ресурсы, ими управляют единообразно через HTTP, а каждый запрос самодостаточен.
REST — не протокол и не библиотека, а набор архитектурных ограничений, сформулированных Роем Филдингом в 2000 году. Если API им следует, его называют RESTful. Эти ограничения не прихоть: каждое из них даёт системе важное свойство — масштабируемость, простоту, надёжность. Разберём идею целиком, а затем самое практичное ограничение — stateless.
Ресурс против представления
Центральное понятие REST — ресурс. Ресурс — это любая значимая «вещь», о которой можно говорить: пользователь, заказ, статья, список комментариев. У каждого ресурса есть адрес — URL, который его идентифицирует:
/users/42 — конкретный пользователь
/users — коллекция пользователей
/users/42/orders — заказы пользователя 42
Здесь важна тонкость, которую часто упускают. Ресурс — это абстрактная сущность, а не конкретные байты. То, что сервер реально присылает клиенту, называется представлением (representation) ресурса. Один ресурс может иметь несколько представлений.
Пользователь 42 как ресурс — это «человек с такими-то свойствами». Его можно представить в JSON:
{"id": 42, "name": "Анна", "email": "[email protected]"}
А можно — в XML, в HTML-странице или даже в CSV. Это разные представления одного ресурса. Клиент заголовком Accept просит нужный формат, сервер отдаёт подходящее представление. Сам ресурс при этом не меняется — меняется только его «снимок».
Расшифруем теперь название: Representational State Transfer — «передача состояния через представления». Клиент и сервер обмениваются не объектами в памяти, а представлениями состояния ресурсов.
Шесть ограничений REST кратко
Филдинг описал шесть ограничений. Пять обязательных и одно опциональное:
| Ограничение | Суть |
| Client-Server | разделение клиента (UI) и сервера (данные); развиваются независимо |
| Stateless | сервер не хранит состояние клиента между запросами; каждый запрос самодостаточен |
| Cacheable | ответы помечаются как кэшируемые или нет; кэш ускоряет систему |
| Uniform Interface | единый способ работы со всеми ресурсами (URL + HTTP-методы) |
| Layered System | между клиентом и сервером могут стоять прокси и балансировщики незаметно |
| Code on Demand | опционально: сервер может прислать исполняемый код (например JS) |
Эти шесть пунктов — суть REST. Ниже разберём два самых важных для дизайна: uniform interface и (особенно подробно) stateless.
Uniform Interface — единый интерфейс
Это ограничение делает REST узнаваемым. Со всеми ресурсами работают одинаково: ресурс адресуется URL, а действие над ним задаётся стандартным HTTP-методом.
| Метод | Смысл | Пример |
GET | получить | GET /users/42 |
POST | создать | POST /users |
PUT | заменить целиком | PUT /users/42 |
DELETE | удалить | DELETE /users/42 |
Прелесть в предсказуемости: узнав, что есть ресурс /orders, вы сразу догадываетесь, что GET /orders/7 вернёт заказ, а DELETE /orders/7 удалит его — не читая документацию. Один набор «глаголов» на всю систему вместо тысяч уникальных функций.
Client-Server — разделение ответственности
Клиент отвечает за интерфейс и взаимодействие с пользователем; сервер — за хранение и логику. Они связаны только контрактом. Это позволяет, например, переписать мобильное приложение с нуля, не трогая сервер, и наоборот — масштабировать сервер, не выпуская новую версию приложения. Разделение — фундамент, на котором стоят остальные ограничения.
Stateless — почему сервер не хранит сессию
Это ограничение чаще всего понимают неправильно, поэтому разберём его обстоятельно. Stateless значит: сервер не хранит состояние клиента между запросами. Каждый запрос обязан содержать всю информацию, нужную для его обработки. Сервер обрабатывает запрос и «забывает» о клиенте.
Сравним два подхода к авторизации.
Stateful (с сохранением состояния, НЕ по REST): при логине сервер создаёт сессию в своей памяти и шлёт клиенту короткий идентификатор. Дальше клиент шлёт только этот ID, а сервер по нему ищет в памяти, кто это.
1. POST /login --> сервер запоминает: сессия abc = пользователь 42
2. GET /profile (abc) --> сервер ищет abc в памяти, узнаёт юзера 42
Stateless (по REST): сервер не хранит ничего. Клиент при каждом запросе предъявляет самодостаточный токен (например JWT), внутри которого зашита вся нужная информация о пользователе, подписанная сервером.
GET /profile HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Host: api.example.com
Сервер проверяет подпись токена, извлекает «пользователь 42» прямо из него — и не лезет ни в какую память о сессиях. Запрос самодостаточен.
Зачем это нужно — выгоды stateless
- Масштабируемость. Если сервер не помнит клиентов, любой из десяти серверов за балансировщиком может обработать любой запрос. Не нужно «приклеивать» клиента к конкретному серверу.
- Надёжность. Упал один сервер — клиент просто повторяет запрос на другой. Память с сессиями не теряется, потому что её нет.
- Простота. Сервер не управляет временем жизни сессий, не чистит «протухшие» — меньше состояния, меньше багов.
Плата за это — каждый запрос немного «тяжелее»: токен и контекст приходится передавать каждый раз. Обычно это приемлемая цена за горизонтальное масштабирование.
Как работает под капотом
Покажем масштабируемость наглядно. Десять одинаковых серверов прячутся за балансировщиком. Клиент шлёт три запроса подряд — и каждый может попасть на разный сервер:
клиент --> [ балансировщик ] --> сервер #3 (запрос 1, токен внутри) клиент --> [ балансировщик ] --> сервер #7 (запрос 2, токен внутри) клиент --> [ балансировщик ] --> сервер #1 (запрос 3, токен внутри)
Серверу #7 неважно, что предыдущий запрос обработал #3 — вся информация снова пришла в запросе. Если бы сервер #3 хранил сессию у себя в памяти, запрос 2 на сервере #7 сломался бы: «не знаю такого клиента». Именно поэтому stateless и масштабируемость — две стороны одной медали.
А где же тогда хранится «состояние»? Состояние ресурсов (данные пользователей, заказов) живёт в общей базе данных — это нормально. REST запрещает хранить именно состояние клиента/сессии в памяти конкретного сервера, а не данные вообще.
Частые ошибки
- Путать ресурс и представление. Ресурс — абстрактная сущность с URL; представление — конкретный JSON/XML, который прислали. Один ресурс — много представлений.
- Считать stateless запретом на базу данных. Нет: данные хранить можно и нужно. Нельзя хранить состояние клиента (сессию) в памяти сервера между запросами.
- Думать, что REST — это протокол. REST — стиль (набор ограничений). Протокол транспорта — HTTP.
- Использовать серверные сессии в памяти и называть API RESTful. Это нарушает stateless и мешает масштабированию.
Итог
- REST — архитектурный стиль из шести ограничений, а не протокол.
- Ресурс — абстрактная сущность с URL; представление — конкретный «снимок» (JSON/XML) этого ресурса.
- Uniform Interface: со всеми ресурсами работают единообразно через URL и HTTP-методы.
- Stateless: сервер не хранит состояние клиента; каждый запрос самодостаточен — это даёт масштабируемость и надёжность.
- Состояние ресурсов в общей базе — это нормально; запрещены именно серверные сессии в памяти.