HATEOAS и гиперссылки: нужно ли
Разбираем самый загадочный уровень зрелости REST — гипермедиа — и честно отвечаем, стоит ли он усилий.
HATEOAS (Hypermedia As The Engine Of Application State) — принцип, по которому ответ сервера несёт не только данные, но и ссылки на доступные дальше действия, чтобы клиент переходил по ним, а не зашивал URL у себя в коде.
Зачем это вообще придумали
Представьте обычный REST-клиент. Он знает наизусть: чтобы оплатить заказ, надо собрать строку /orders/42/pay и послать туда POST. Этот шаблон URL зашит в коде мобильного приложения, фронтенда и десятка интеграций партнёров. Пока схема URL не меняется, всё хорошо. Но стоит вам переименовать ресурс или ввести правило «оплатить можно только заказ в статусе new» — и каждый клиент должен узнать об этом из документации и обновиться. Сервер не может управлять поведением клиента, он лишь отдаёт данные.
HATEOAS переворачивает идею: пусть сервер сам подскажет, что можно делать дальше. Если заказ оплачиваем — в ответе будет ссылка pay. Если уже оплачен — ссылки не будет, зато появится refund. Клиент перестаёт конструировать URL и начинает следовать по ним. Это ровно то, как работает обычный сайт в браузере: вы не пишете адреса руками, вы кликаете по ссылкам, которые отдала страница.
Уровень 3 модели Ричардсона
Леонард Ричардсон описал зрелость REST-API четырьмя уровнями. Уровень 0 — один эндпоинт и один глагол (типичный «RPC поверх HTTP»). Уровень 1 — появляются ресурсы (/orders, /users). Уровень 2 — корректно используются HTTP-методы и коды статусов (GET, POST, 404, 201). Большинство «хороших» API живут именно здесь. Уровень 3 — гипермедиа: к корректным ресурсам и методам добавляются ссылки на действия. Многие считают, что без уровня 3 API «не настоящий REST», но на практике уровень 2 покрывает подавляющее большинство задач.
Как выглядят ссылки в ответе
Сама идея простая: рядом с данными ресурса кладём объект со ссылками. Часто его называют _links или related. Каждая ссылка — это пара «имя отношения → адрес». Имя отношения (link relation, rel) описывает смысл перехода: self — ссылка на сам ресурс, next — следующая страница, pay — действие оплаты.
{
"id": 42,
"status": "new",
"total": 1990,
"_links": {
"self": { "href": "/orders/42" },
"pay": { "href": "/orders/42/payment", "method": "POST" },
"cancel": { "href": "/orders/42", "method": "DELETE" }
}
}
Клиент читает не «жёсткий» путь, а отношение: «есть ли в ответе ссылка pay?». Если есть — показывает кнопку «Оплатить» и шлёт запрос на href. Сервер при этом волен поменять сам адрес /orders/42/payment на что угодно — клиент не сломается, потому что не знает этот URL заранее.
Форматы: HAL и JSON:API
Чтобы клиенты и серверы понимали друг друга одинаково, придумали стандартные форматы гипермедиа. Два самых известных:
| Формат | Где ссылки | Особенность |
| HAL | в ключе _links | минималистичный; вложенные ресурсы — в _embedded |
| JSON:API | в ключе links у ресурса и связей | строгая спецификация: data, relationships, included |
Пример HAL со встроенным ресурсом — заказ сразу содержит данные покупателя, чтобы клиент не делал второй запрос:
{
"id": 42,
"total": 1990,
"_links": { "self": { "href": "/orders/42" } },
"_embedded": {
"customer": {
"id": 7,
"name": "Анна",
"_links": { "self": { "href": "/users/7" } }
}
}
}
Как работает под капотом
Самое важное в HATEOAS происходит не в данных, а в голове клиента. «Тонкий» гипермедийный клиент держит у себя один-единственный жёсткий адрес — точку входа, например /. Всё остальное он узнаёт в рантайме, переходя по ссылкам.
GET / корневой документ
│ _links: { orders: /orders }
▼
GET /orders список заказов
│ _links: { next: /orders?page=2 }
│ каждый заказ: _links.self -> /orders/42
▼
GET /orders/42 один заказ
│ _links: { pay: /orders/42/payment }
▼
POST /orders/42/payment действие, которое РАЗРЕШИЛ сервер
Сервер на каждом шаге формирует ссылки динамически: смотрит на статус ресурса и права пользователя и решает, какие действия положить в ответ. Если заказ уже оплачен — ссылки pay просто не будет, и клиент физически не сможет инициировать повторную оплату по «угаданному» URL, потому что не знает его. Так бизнес-правила переезжают на сервер, а клиент становится тоньше и устойчивее к изменениям.
Плюсы
- Обнаруживаемость. Клиент видит доступные действия прямо в ответе, документация нужна меньше.
- Эволюция URL. Сервер может менять адреса ресурсов, не ломая клиентов, которые ходят по ссылкам.
- Состояние на сервере. Допустимые переходы (оплатить/отменить/вернуть) задаёт сервер, а не зашитая в клиенте логика.
Минусы и почему многие отказываются
- Сложность. Генерировать ссылки на сервере и обходить их на клиенте дороже, чем просто отдать JSON.
- Размер ответов. Блоки
_linksраздувают тело, особенно в больших списках. - Мало кто реально следует. Большинство клиентов всё равно зашивают URL «как быстрее», и гипермедиа простаивает.
- Нет единого стандарта. HAL, JSON:API, Siren, кастомные
_links— экосистема разрозненна.
Частые ошибки
«Сделали _links, но клиент всё равно строит URL сам.» Тогда вы платите за сложность, не получая выгоды. HATEOAS оправдан, только если клиент действительно ходит по ссылкам.
Путают rel и href. Менять можно адрес (href), а имя отношения (self, next) — это контракт, его трогать нельзя, иначе клиент потеряет смысл ссылки.
Считают, что без уровня 3 «это не REST». На практике корректный уровень 2 (ресурсы + методы + коды) решает почти всё; гипермедиа — инструмент под конкретные задачи, а не обязанность.
Прагматичный вывод: когда оправдано
HATEOAS имеет смысл, когда у вас долгоживущий публичный API с множеством независимых клиентов, которых нельзя обновить одновременно; когда переходы между состояниями ресурса сложны и их хочется централизовать на сервере; либо когда вы строите общий «движок» вроде платёжного или документооборотного API. Для внутреннего API одного-двух фронтендов, которые катятся вместе с бэкендом, гипермедиа обычно избыточен — проще зафиксировать контракт в OpenAPI и жить на уровне 2.
Итоги
- HATEOAS — уровень 3 Ричардсона: сервер кладёт в ответ ссылки на доступные действия.
- Клиент следует по ссылкам (rel), а не конструирует URL — это даёт обнаруживаемость и свободу менять адреса.
- Стандартные форматы: HAL (
_links/_embedded) и JSON:API (links/relationships). - Платой за это идёт сложность; внедряйте гипермедиа осознанно, под публичные и долгоживущие API.