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.
Проверьте себя
1. Что добавляет уровень 3 модели Ричардсона к корректному REST-API уровня 2?
AПоддержку HTTP-методов и кодов статусов
BРазбиение на ресурсы с понятными URL
CГиперссылки на доступные действия прямо в ответе (HATEOAS)
DОбязательное шифрование TLS
2. Главная практическая выгода HATEOAS для эволюции API — это:
AСервер может менять адреса ресурсов, не ломая клиентов, которые ходят по ссылкам
BОтветы становятся меньше по размеру
CКлиенту больше не нужна авторизация
DJSON автоматически валидируется по схеме
3. Какой формат хранит ссылки в ключе _links, а вложенные ресурсы — в _embedded?
AJSON:API
BHAL
CGraphQL
DProtobuf