Разработчики часто неверно понимают концепцию передачи состояния представления (REST). Большинство ошибок связаны с трактовкой архитектурного ограничения HATEOAS. В этой статье мы разберем популярные заблуждения, связанные с REST, и подробно остановимся на HATEOAS. В конце текста на примере имитации конечного автомата — кухонного тостера — рассмотрим, как гипермедиа может использоваться в REST API для управления состояниями.
Примечание: Это адаптированный перевод статьи Designing a True REST State Machine Билла Доррфельда, технического журналиста и специалиста по API. Повествование ведётся от лица автора оригинала.
Концепция гипермедиа сформировалась в 1941 году, когда аргентинский писатель Хорхе Луис Борхес написал «Сад расходящихся тропок» — рассказ, в котором страницы текста ссылаются друг на друга. Вероятно, это первый в истории пример гипертекста (на самом деле впервые гипертекст использовался в романе Джеймса Джойса «Улисс», — прим. редакции). Сейчас в массовой культуре есть множество примеров реляционных связей — от сюжета видеоигры Bioshock до серии детских романов-ужасов Роберта Стайна Goosebumps, — но во времена Борхеса такой прием был беспрецедентным.
Гипермедиа — система организации информации, элементы которой взаимосвязаны и, кроме самого гипертекста, включают в себя видео, картинки, аудио и другие типы контента.
Прокачивайте свой уровень программирования: На Хекслете есть несколько десятков треков — специальных курсов для опытных программистов, позволяющие повысить уровень компетентности разработчика в разных направлениях.
Затем произошло несколько событий, которые помогли REST сформироваться в концепцию с четкой структурой:
Все технические аспекты, которые определяют REST, трудно раскрыть в виде статьи в блоге — это слишком объемная тема. Поэтому предлагаю пойти от обратного: рассмотреть четыре распространенных заблуждения — они помогут нам понять, чем REST точно не является.
CRUD — акроним, который обозначает четыре базовые функции при работе с персистентными хранилищами данных: «создание, чтение, редактирование, удаление». CRUD соответствует действиям в SQL, но он плохо соотносится с методами HTTP.
Хотя GET и DELETE в REST и CRUD совпадают, POST, PUT и PATCH отвечают за разные операции. Например, в REST POST означает не только «создать» — это универсальный метод. С его помощью туннелируется весь протокол SOAP при использовании с HTTP.
Поскольку HTTP-методы не соответствуют CRUD, теоретик и популяризатор REST Асбьёрн Ульсберг утверждает, что создатели различных API должны подумать, как они могут описывать API иным способом: «Не ограничивайте себя CRUD при разработке REST API. Прочитайте спецификацию и поймите семантику каждого метода, а затем правильно используйте ее».
Если перефразировать, смысл этой цитаты сводится к тому, что REST — это стиль архитектуры, а не протокол. Таким образом, ошибочно называть «RESTful» HTTP API с операциями CRUD.
Унифицированные идентификаторы ресурсов (URI) — основа концепции REST. Они позволяют определять ресурсы и действия с ними. Однако многие разработчики ошибочно считают, что могут отличить качество построения REST API просто на основе того, как структурирован URI. Проведем эксперимент: сможете ли вы сказать, какой URI «более RESTful»?
Тот факт, что мы указали URI, метод и описание для каждого вызова API, еще не значит, что мы создали REST API — мы просто задокументировали наши URI, как если бы мы задокументировали операции удаленного вызова процедур (RPC). Ульсберг отмечал, что такой подход усложняет работу и лишает сервис гибкости.
Представим, что у нас есть таблица базы данных под названием «Referer». После нескольких лет использования мы заметили, что имя базы написано с ошибкой и решили изменить его на «Referrer». Клиенты уже взаимодействуют со старым именем таблицы в своих SQL-операторах, поэтому обновить имя базы будет крайне сложно — прежде придется обновить все клиенты.
Читайте также: Как устроен функциональный диалект Лиспа Clojure и почему использующие его программисты восхищаются им
То же самое с API: если мы решим обновить один из /blogposts/ в URI из примеров, указанных выше, обновления потребуют все клиенты. Это приведет к созданию второй версии — то есть к необходимости обновить документацию и все клиенты. Резюмируя, можно сказать, что строго запрограммированное управление версиями в URI — это боль.
В интервью Майку Амудсену в 2014 году Филдинг сказал следующее: «Гипермедиа как механизм управления состоянием приложения — это ограничение REST. Это не одна из опций и не идеал, к которому нужно стремиться. Гипермедиа — это данность и ограничение. Вы либо принимаете их, либо не занимаетесь REST».
REST состоит из 6 основных ограничений — так называемых ограничений Филдинга. Он выглядит так:
При этом последний пункт — «Единообразие интерфейса» — состоит из еще нескольких подпунктов:
Среди них HATEOAS, вероятно, самое важное и уникальное, но наименее понятное условия REST.
По сути, HATEOAS или гипермедиа как механизм управления состоянием, — это подход к описанию ресурсов API. С его помощью можно установить инверсию контроля за ресурсами, которые клиент может вызвать, вместо того, чтобы просто перечислять каждый из них и указывать список функций, которые можно с ними выполнить. Именно благодаря этому сервер может отвечать за состояние ресурса.
Чтобы лучше понять эту идею, Ульсберг советует воспользоваться методом, предложенным Дональдом Норманом в книге «Дизайн повседневных вещей». Так же, как чашка сделана для того, чтобы ее взяли за ручку и подняли, а кнопка — чтобы быть нажатой, гипермедиа хочет сообщить вам, что делать с тем или иным ресурсом. Гипермедиа — это ссылки и метаданные для операций, которые помогают разработчикам или машинам выполнять дополнительные действия.
Читайте также: О релевантности принципов объектно-ориентированного программирования SOLID
Асбьёрн Ульсберг описывает гипермедиа так: «Если вы посмотрите на гипермедиа как на рецепт того, как должен выглядеть следующий запрос, вы поймете, что такое гипермедиа».
Разберем концепцию гипермедиа как механизма состояния приложения на примере простого конечного автомата — тостера, подключенного к интернету вещей (IoT), которым можно управлять через интернет.
Конечный автомат — это абстрактная модель, которая содержит конечное количество состояний чего-либо. В нашем случае тостер запускается после включения — переходит в состояние нагрева. Затем он достигает верхнего предела температуры и переходит в состояние ожидания, снижая температуру. Этот цикл продолжается до тех пор, пока кусок хлеба не поджарится достаточно хорошо — после этого тостер отключается.
Как управлять тостером с помощью REST и гипермедиа? Филдинг писал, что каждая страница в сети представляет собой отдельное состояние одного ресурса, которое можно получить с помощью вызова GET (этот процесс показан в таблице выше). То есть вы можете получить какие-либо данные с помощью идентификатора в URI.
Попробуем сделать это: для начала вызовем наш тостер с помощью GET:
GET /toaster HTTP/1.1
Ответ будет выглядеть примерно так:
HTTP/1.1 200 OK
{
"Id": "/toaster",
"state": "off",
"operations": [{
"rel": "on",
"method": "PUT",
"href": "/toaster",
"Expects": { "state": "on"}
}]
}
В ответе содержится идентификатор, который показывает, с каким ресурсом мы работаем. Следом отображается текущее состояние ресурса, а ниже — список возможных операций, которые с ним можно совершить.
Теперь попробуем изменить состояние тостера. Для этого отправим HTTP-запрос PUT:
PUT /toaster HTTP/1.1
{
"state": "on"
}
Ответ будет выглядеть примерно так:
HTTP/1.1 200 OK
{
"Id": "/toaster",
"state": "on",
"strength": 0,
"operations": [ {
"rel": "on",
"method": "PUT",
"href": "/toaster",
"expects": { "state": "off" }
}, {
"rel": "strength",
"method": "PUT",
"href": /fcesj48fl29304d827434j
"expects": {
"strength": [1,2,3,4,5,6]
}
}]
Хорошо, теперь мы включили тостер. Однако в ответе видно, что его мощность по-прежнему на нуле — он все еще не нагревается. Попробуем вызвать изменение мощности:
PUT /toaster HTTP/1.1
{
"strength": 3
}
Мы увеличили мощность — теперь в ответе видно, что тостер нагревается.
HTTP/1.1 200 OK
{
"Id": "/toaster",
"state": "heating",
"strength": 3,
"operations": [ {
"rel": "on",
"method": "PUT",
"href": "/toaster",
"expects": { "state": "off" }
}, {
"rel": "strength",
"method": "PUT",
"href": /fcesj48fl29304d827434j
"expects": {
"strength": [1,2,3,4,5,6]
}
}]
}
Теперь мы можем перевести тостер в режим ожидания и снова увеличить мощность. Однако вместо этого отправим еще один вызов GET на идентификатор устройства:
HTTP/1.1 200 OK
{
"Id": "/toaster",
"state": "idle",
"strength": 3,
"operations": [ {
"rel": "on",
"method": "PUT",
"href": "/toaster",
"expects": { "state": "off" }
}, {
"rel": "strength",
"method": "PUT",
"href": /fcesj48fl29304d827434j
"expects": {
"strength": [1,2,3,4,5,6]
}
}]
}
С момента нашего последнего запроса тостер перешел в состояние ожидания. В ответе видно, что мы все еще можем отключить его или отрегулировать температуру, усилив мощность нагрева.
Если подождать еще несколько минут, следующий запрос к конечному автомату, скорее всего, приведет его к состоянию «выключение» или «выключено» — то есть в то состояние, с которого мы начинали.
Сеть функционирует как набор идей, связанных друг с другом гипертекстом. Ульсберг считает, что веб-API должны имитировать этот принцип, а важным аспектом в реализации API является гипермедиа. Для тех, кто только начинает работать с REST, создание HATEOAS-совместимого API — это огромный шаг вперед по сравнению с API-интерфейсами в стиле RPC.
«Если вы используете гипермедиа, вы можете добавлять отношения, ссылки и операции к ресурсам, не нарушая работу существующих клиентов, и наделяя новых клиентов новыми функциями», — писал Ульсберг.
Такой подход подразумевает переосмысление традиционного подхода к управлению версиями. Мнения Ульсберга и Филдинга в этом вопросе сходятся: управление версиями в REST — плохая идея. Когда вы в последний раз видели номер версии на веб-сайте? HTML не требует контроля версий, и JSON тоже в нем не нуждается.
Уделяя больше внимания ресурсам как конечному автомату, мы можем сообщать потребителям текущее состояние, с помощью которого мы совершаем операции. Такой подход снижает степень связанности и в бизнес-доменах, где состояние приложения состоит из множества сложных и взаимозависимых факторов, и значительно упрощает работу клиента. Именно в этом сила REST и гипермедиа.
Учитесь бесплатно: На Хекслете есть много бесплатных курсов по программированию, логике, английскому языку и различным инструментам для разработки