Разработчики часто неверно понимают концепцию передачи состояния представления (REST). Большинство ошибок связаны с трактовкой архитектурного ограничения HATEOAS. В этой статье мы разберем популярные заблуждения, связанные с REST, и подробно остановимся на HATEOAS. В конце текста на примере имитации конечного автомата — кухонного тостера — рассмотрим, как гипермедиа может использоваться в REST API для управления состояниями.
Примечание: Это адаптированный перевод статьи Designing a True REST State Machine Билла Доррфельда, технического журналиста и специалиста по API. Повествование ведётся от лица автора оригинала.
История REST и гипермедиа
Концепция гипермедиа сформировалась в 1941 году, когда аргентинский писатель Хорхе Луис Борхес написал «Сад расходящихся тропок» — рассказ, в котором страницы текста ссылаются друг на друга. Вероятно, это первый в истории пример гипертекста (на самом деле впервые гипертекст использовался в романе Джеймса Джойса «Улисс», — прим. редакции). Сейчас в массовой культуре есть множество примеров реляционных связей — от сюжета видеоигры Bioshock до серии детских романов-ужасов Роберта Стайна Goosebumps, — но во времена Борхеса такой прием был беспрецедентным.
Гипермедиа — система организации информации, элементы которой взаимосвязаны и, кроме самого гипертекста, включают в себя видео, картинки, аудио и другие типы контента.
Прокачивайте свой уровень программирования: На Хекслете есть несколько десятков треков — специальных курсов для опытных программистов, позволяющие повысить уровень компетентности разработчика в разных направлениях.
Затем произошло несколько событий, которые помогли REST сформироваться в концепцию с четкой структурой:
- 1963: Тед Нельсон ввел термины гипертекста и гипермедиа.
- 1968: Исследователь человеко-машинного интерфейса(Human-Machine Intelligence, HMI) Дуглас Энгельбарт на презентации, которую позже окрестили «матерью всех демонстраций», представил систему NLS – oN-Line System и первую компьютерную мышь. Этот момент можно назвать началом обработки текста в современном виде.
- 1987: Сотрудник Apple Билл Аткинсон создал HyperCard — первую успешную реализацию гипермедиа до появления Всемирной паутины.
- 1989: Тим Бернерс-Ли из ЦЕРНа создал Всемирную паутину и представил первую успешную реализацию протокола передачи гипертекста (HTTP) между клиентом и сервером.
- 2000: Рой Филдинг, соавтор спецификации HTTP и URI, написал докторскую диссертацию «Архитектурные стили и проектирование сетевых архитектур программного обеспечения», где он описал передачу репрезентативного состояния, или REST. Именно Филдинг считается создателем концепции REST в ее нынешнем виде.
Что такое REST
Все технические аспекты, которые определяют REST, трудно раскрыть в виде статьи в блоге — это слишком объемная тема. Поэтому предлагаю пойти от обратного: рассмотреть четыре распространенных заблуждения — они помогут нам понять, чем REST точно не является.
Заблуждение №1: REST — это просто CRUD
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.
Заблуждение №2: некоторые конструкции URI «более RESTful», чем другие
Унифицированные идентификаторы ресурсов (URI) — основа концепции REST. Они позволяют определять ресурсы и действия с ними. Однако многие разработчики ошибочно считают, что могут отличить качество построения REST API просто на основе того, как структурирован URI. Проведем эксперимент: сможете ли вы сказать, какой URI «более RESTful»?
- http://hexlet.io/authors/contributor?author=doerrfeld
- http://api.hexlet.io/blogpost/getPostById?id=47
- http://api.hexlet.io/blogpost/47/edit-form
- http://api.hexlet.io/blogpost/47
- http://api.hexlet.io/128ndoels-8asdf-12d5-39d3
Тот факт, что мы указали URI, метод и описание для каждого вызова API, еще не значит, что мы создали REST API — мы просто задокументировали наши URI, как если бы мы задокументировали операции удаленного вызова процедур (RPC). Ульсберг отмечал, что такой подход усложняет работу и лишает сервис гибкости.
Заблуждение №3: API-интерфейсам REST нужны версии
Представим, что у нас есть таблица базы данных под названием «Referer». После нескольких лет использования мы заметили, что имя базы написано с ошибкой и решили изменить его на «Referrer». Клиенты уже взаимодействуют со старым именем таблицы в своих SQL-операторах, поэтому обновить имя базы будет крайне сложно — прежде придется обновить все клиенты.
Читайте также: Как устроен функциональный диалект Лиспа Clojure и почему использующие его программисты восхищаются им
То же самое с API: если мы решим обновить один из /blogposts/ в URI из примеров, указанных выше, обновления потребуют все клиенты. Это приведет к созданию второй версии — то есть к необходимости обновить документацию и все клиенты. Резюмируя, можно сказать, что строго запрограммированное управление версиями в URI — это боль.
Заблуждение №4. Гипермедиа необязательна для REST API
В интервью Майку Амудсену в 2014 году Филдинг сказал следующее: «Гипермедиа как механизм управления состоянием приложения — это ограничение REST. Это не одна из опций и не идеал, к которому нужно стремиться. Гипермедиа — это данность и ограничение. Вы либо принимаете их, либо не занимаетесь REST».
REST состоит из 6 основных ограничений — так называемых ограничений Филдинга. Он выглядит так:
- Клиент-сервер
- Отсутствие состояния
- Кэшируемость
- Многоуровневая система
- Код по требованию
- Единообразие интерфейса
При этом последний пункт — «Единообразие интерфейса» — состоит из еще нескольких подпунктов:
- Определение ресурсов
- Управление ресурсами через представление
- Самодостаточные сообщение
- Гипермедиа как механизм управления состоянием приложения (HATEOAS)
Среди них HATEOAS, вероятно, самое важное и уникальное, но наименее понятное условия REST.
По сути, HATEOAS или гипермедиа как механизм управления состоянием, — это подход к описанию ресурсов API. С его помощью можно установить инверсию контроля за ресурсами, которые клиент может вызвать, вместо того, чтобы просто перечислять каждый из них и указывать список функций, которые можно с ними выполнить. Именно благодаря этому сервер может отвечать за состояние ресурса.
Чтобы лучше понять эту идею, Ульсберг советует воспользоваться методом, предложенным Дональдом Норманом в книге «Дизайн повседневных вещей». Так же, как чашка сделана для того, чтобы ее взяли за ручку и подняли, а кнопка — чтобы быть нажатой, гипермедиа хочет сообщить вам, что делать с тем или иным ресурсом. Гипермедиа — это ссылки и метаданные для операций, которые помогают разработчикам или машинам выполнять дополнительные действия.
Читайте также: О релевантности принципов объектно-ориентированного программирования SOLID
Асбьёрн Ульсберг описывает гипермедиа так: «Если вы посмотрите на гипермедиа как на рецепт того, как должен выглядеть следующий запрос, вы поймете, что такое гипермедиа».
Пример конечного автомата: IoT-тостер
Разберем концепцию гипермедиа как механизма состояния приложения на примере простого конечного автомата — тостера, подключенного к интернету вещей (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 и гипермедиа.
Учитесь бесплатно: На Хекслете есть много бесплатных курсов по программированию, логике, английскому языку и различным инструментам для разработки