Что такое REST API и почему ваш — вероятно, не REST
Большинство разработчиков строят не REST API, а JSON-over-HTTP. Рой Филдинг определил 6 ограничений — разбираем каждое и выясняем, почему HATEOAS почти везде пропускают.
Большинство разработчиков хотя бы раз строили «REST API». Мало кто читал диссертацию, которая его определяет. Этот разрыв между популярным пониманием и оригинальной спецификацией порождает повторяющиеся архитектурные проблемы и нестабильность API.
REST API — это веб-сервис, удовлетворяющий шести архитектурным ограничениям, выведенным Роем Филдингом в докторской диссертации «Architectural Styles and the Design of Network-based Software Architectures» (UC Irvine, 2000 год). Начав с «нулевого стиля» — пустого набора без ограничений — Филдинг добавлял каждое из них последовательно, анализируя порождаемые ими свойства распределённых гипермедиа-систем (систем, где переходы между состояниями описаны прямо в ответах сервера). Результат получил название «Передача репрезентативного состояния» (Representational State Transfer).
Индустрия взяла название и проигнорировала большинство ограничений. «REST» теперь означает «любой API, который отправляет JSON по HTTP». Если вы строите публичный API или API для команд за пределами вашей организации — пропущенные ограничения начинают стоить денег.
Ключевые выводы
REST — это шесть архитектурных ограничений, сформулированных Роем Филдингом в 2000 году, а не «любой JSON-over-HTTP API».
Большинство API выполняют лишь 2–3 ограничения из шести: клиент-сервер, stateless и частично слоистую систему.
Самое игнорируемое ограничение — HATEOAS: сервер передаёт клиенту список доступных действий прямо в ответе.
HATEOAS решает три дорогостоящие проблемы: пагинацию, версионирование API и обнаруживаемость ресурсов.
Для небольшой команды с одним потребителем пропуск HATEOAS оправдан. Для публичного API — нет.
Шесть ограничений REST API: разбор по порядку
Ограничение 1: Клиент-сервер
Клиент и сервер имеют разные зоны ответственности. Клиент отвечает за интерфейс, сервер — за данные и логику. Большинство API справляются с этим по умолчанию. Нарушение появляется, когда сервер начинает диктовать, как клиент должен отображать информацию.
Например, если API возвращает displayOrder: 3 и buttonColor: "#ff0000" для какого-либо действия — это нарушение. Порядок отображения должен следовать из позиции элементов в ответе. Цвет должен определяться семантическим свойством вроде class: ["danger"], которое каждый клиент интерпретирует самостоятельно.
Ограничение 2: Stateless (без состояния)
Каждый запрос содержит всю информацию, необходимую серверу для его обработки. Сервер не хранит состояние сессии между вызовами.
Если вы отправляете GET /path-1 с сессионной cookie, и сервер ищет её в памяти, чтобы получить ваш ID пользователя — это серверное состояние. Stateless-версия включает ID прямо в запрос: JWT или тело POST-запроса переносят его вместе с запросом и могут вернуть в ответе для повторного использования клиентом.
Ограничение 3: Кэшируемость
Ответы должны быть явно или неявно помечены как кэшируемые или некэшируемые. Клиент или промежуточный узел может повторно использовать закэшированные ответы, не обращаясь к серверу. Филдинг рассматривал кэшируемость как архитектурную задачу первого класса, повышающую эффективность и воспринимаемую производительность за счёт снижения средней задержки.
Большинство JSON API полностью игнорируют кэширование. Вы отправляете GET /articles/42, а в ответе нет ни Cache-Control, ни ETag, ни Last-Modified. Клиент обращается к серверу каждый раз, даже если статья не менялась неделями.
Ограничение 4: Единый интерфейс (Uniform Interface)
Это главное ограничение. Филдинг разбил его на четыре подограничения — три описываются ниже, четвёртое (HATEOAS) вынесено в отдельный раздел из-за его значимости.
4.1 URI идентифицируют ресурсы. Единообразие здесь — это сама спецификация URI: схема, authority, путь, запрос, фрагмент. Ограничение ничего не говорит о структуре сегмента пути: /articles/42, /x?id=42 и /a/b/c — всё это валидные URI. «Используйте чистые URL-пути» — популярное соглашение и хороший SEO-инструмент, но не то, что требует Филдинг.
4.2 Управление ресурсами через представления. Вы выполняете GET в /whatever, чтобы получить представление ресурса. Заголовок Content-Type сообщает серверу формат тела запроса. Заголовок Accept сообщает, какие медиатипы (форматы обмена данными) поддерживает клиент для ответа.
4.3 Самоописывающие сообщения. Ответ с Content-Type: application/vnd.collection+json сообщает клиенту, как разбирать тело, без каких-либо предположений.
Ограничение 4.4: HATEOAS — то, что пропускают почти все
HATEOAS (Hypermedia As The Engine Of Application State) — четвёртое подограничение Uniform Interface. С первыми тремя большинство API справляются. HATEOAS — место, где останавливается почти каждый.
Разница хорошо видна на примере API для списка чтения. Без HATEOAS вы получаете просто данные, похожие на запись в базе:
Клиент ничего не знает о том, что он может сделать дальше. Чтобы отметить статью как прочитанную, клиент уже должен знать endpoint: PATCH /articles/42 с {"status": "read"}. Разработчик захардкодил эти знания, прочитав документацию. Сам API их не сообщил.
С HATEOAS сервер сообщает клиенту о доступных действиях в стандартизированном виде. Вот тот же ответ с использованием Siren — одного из стандартизированных медиатипов для гипермедиа-API:
Клиент не хардкодит URL и HTTP-методы. Массив actions сообщает, что можно сделать. Навигация приходит из links. Если статья уже прочитана — сервер исключает действие mark-as-read из ответа. Кнопка исчезает в UI. Без единого условного выражения в клиентском коде.
Сервер добавляет новое действие — и каждый клиент подхватывает его при следующем запросе, без деплоя. Это принципиальное отличие: сервер управляет доступными переходами состояния.
Ограничение 5: Слоистая система
Клиент не может определить, общается ли он с конечным сервером или с промежуточным узлом. Балансировщики нагрузки, CDN и API-шлюзы должны быть прозрачны для вызывающей стороны. Большинство API выполняют это ограничение автоматически. Самый распространённый вид нарушения — когда сообщения об ошибках раскрывают имя хоста бэкенд-сервиса, тем самым нарушая прозрачность слоёв.
Ограничение 6: Код по требованию (необязательное)
Сервер может передавать исполняемый код клиенту. Думайте о JavaScript, подключаемом через тег <script> на веб-странице. Для API это ограничение почти не применимо. Филдинг сделал его единственным необязательным.
Что HATEOAS решает на практике
Филдинг разработал HATEOAS для решения тех самых проблем, с которыми API-команды сейчас борются вручную, многократно и каждый раз по-разному.
Пагинация
Без HATEOAS каждый API изобретает собственную схему. Один использует page и pageSize. Другой — offset и limit. Третий — токены на основе курсора. С HATEOAS сервер включает ссылку с отношением «следующая страница». Клиент следует этому отношению. Схема пагинации может измениться, не сломав ни одного клиента: клиент не знал деталей схемы и не знает формата URL.
Версионирование
Без HATEOAS команды версионируют API через URL-пути (/v1/, /v2/) или кастомные заголовки вроде X-API-Version. Поддержка нескольких версий занимает месяцы, клиенты привязываются к версии и ломаются при её выводе из эксплуатации. С HATEOAS сервер вводит новые действия, добавляя ссылки. Старые ссылки продолжают работать.
Обнаруживаемость
Без HATEOAS первый шаг разработчика — чтение Swagger-документации, второй — хардкодинг каждого endpoint в клиент. С HATEOAS корень API возвращает ссылки на все доступные ресурсы. Клиент исследует API так же, как браузер исследует веб-сайт.
Сколько ограничений выполняет ваш API
Итого шесть ограничений. Большинство API удовлетворяют двум-трём: клиент-сервер, слоистую систему и частичную безгосударственность. Большинство нарушают кэшируемость по умолчанию. Большинство полностью игнорируют HATEOAS.
- Клиент-сервер — разделение ответственности за интерфейс и данные
- Stateless — каждый запрос самодостаточен, без серверных сессий
- Кэшируемость — явные заголовки Cache-Control, ETag, Last-Modified
- Единый интерфейс — URI, представления, самоописывающие сообщения и HATEOAS
- Слоистая система — прозрачность промежуточных узлов
- Код по требованию — опционально, для API почти не применимо
Это не оценка. Это карта компромиссов, которые вы приняли — намеренно или случайно. Для небольшой команды с одним потребителем и Slack-каналом для координации пропуск HATEOAS оправдан. Публичный API с сотнями потребителей платит за каждый пропущенный раунд миграции версий. Подробнее об эволюции REST-архитектуры — в главе 5 диссертации Филдинга.
Часто задаваемые вопросы
Что такое REST API на самом деле?
REST API — это веб-сервис, удовлетворяющий шести архитектурным ограничениям Роя Филдинга (2000 год): клиент-сервер, stateless, кэшируемость, единый интерфейс (включая HATEOAS), слоистая система и опциональный код по требованию. Большинство API, называемых REST, на самом деле являются «HTTP API с JSON» и выполняют лишь 2–3 из шести ограничений.
Что такое HATEOAS и зачем он нужен?
HATEOAS (Hypermedia As The Engine Of Application State) — ограничение, при котором сервер включает в каждый ответ список доступных действий и ссылок. Клиент не хардкодит URL и не читает документацию — он следует ссылкам, как браузер следует гиперссылкам. Это решает проблемы версионирования, пагинации и обнаруживаемости без изменений на стороне клиента.
Нужно ли реализовывать HATEOAS в каждом API?
Нет. Для небольшого внутреннего API с единственным потребителем затраты не оправданы. Стоимость растёт с количеством потребителей, миграциями версий и количеством ломающих изменений. Публичные API с внешними потребителями получают от HATEOAS наибольшую выгоду.
Какие медиатипы поддерживают HATEOAS?
Несколько стандартизированных форматов: Siren (application/vnd.siren+json) описывает действия, поля и ссылки; HAL — Hypertext Application Language (application/hal+json) — предоставляет встроенные ресурсы и ссылки; JSON:API (application/vnd.api+json) определяет структуру отношений. Каждый из них позволяет создавать клиенты, не знающие конкретных endpoint.
Почему большинство API не соответствуют стандарту REST?
Преимущественно из-за неосведомлённости и прагматики. Термин REST стал синонимом «JSON API», и большинство разработчиков учились REST именно в этом упрощённом понимании. Реализация HATEOAS требует выбора медиатипа, обучения команды и изменения клиентского кода — инвестиции, которые малые команды не готовы делать.
Выводы
Я разочарован тем, что многие не знакомы с 15-летними исследованиями в области гипермедиа, которые стоят за REST. Большинство так называемых REST API — это просто удалённые вызовы процедур через HTTP.
Пройдитесь по шести ограничениям и посчитайте, сколько из них выполняет ваш API. Это даст не оценку, а карту принятых компромиссов — осознанных или случайных. Упражнение отвечает на один вопрос: ваш API — это Representational State Transfer или просто HTTP-транспорт, закрытый для расширения?
Оригинальная статья: Fagner Brack — What Is a REST API, and Why Yours Probably Isn't One. Первоисточник: Глава 5 диссертации Роя Филдинга, UC Irvine.