Службы RESTful, часть II: ограничения и цели

В Части I этой серии я писал о HTTP и его конструкциях в применении к дизайну веб-сервисов.

HTTP - это лишь небольшая часть того, что используется при написании современных веб-сервисов.

Этот пост о том, как вы применяете эти конструкции для создания поддерживаемых и надежных сервисов.

Определение REST

REST расшифровывается как RE презентационный S tate T ransfer. Это архитектурный стиль. Это означает, что REST не навязывает формального стандарта для определения того, является ли веб-служба RESTful. Скорее, у него есть набор широких ограничений, каждое из которых имеет в виду конкретную цель.

Эти ограничения были впервые введены Роем Филдингом, который был одним из соавторов спецификации HTTP еще в 2000 году.

Ограничения Филдинга

Филдинг создал эти ограничения с конечной целью сделать приложения более быстрыми, надежными и простыми в масштабировании.

Как разработчик веб-сервисов, ваш сервис должен стараться соблюдать эти ограничения как можно точнее, чтобы воспользоваться их преимуществами. Итак, давайте погрузимся в них.

Ограничение # 1: архитектура клиент-сервер

Первое ограничение, предложенное REST, - это отделение сервера от его клиента. Вы должны поощрять разделение проблем между вашим сервером и клиентами, где это возможно. Ваша цель должна заключаться в максимальном разделении труда и минимизации дублирования между ними.

Сервер или бэкэнд обычно отвечает за хранение постоянных данных вашего приложения вместе со всей бизнес-логикой, необходимой для взаимодействия с ними. Это может включать аутентификацию пользователя, авторизацию, проверку данных и так далее.

Клиент, или внешний интерфейс, отвечает за отправку запросов к службе, а затем за выполнение каких-либо значимых действий с полученным ответом.

Сам клиент может быть веб-службой, и в этом случае он просто потребляет данные. В качестве альтернативы он может быть обращен к пользователю. Примером этого может быть веб-приложение или мобильное приложение. Здесь он также отвечает как за представление данных пользователю, так и за предоставление интерфейса для взаимодействия пользователя с ним .

Вы должны иметь возможность рассматривать каждый из этих двух компонентов как черный ящик по отношению друг к другу. Таким образом, они могут быть изменены независимо. Это поощряет модульность в приложении.

Эта концепция не уникальна для приложений RESTFul или даже веб-приложений. Большинство разработчиков все равно пытаются разбить свои проекты на независимые компоненты. Но, заявив, что это явное ограничение RESTful-дизайна, Филдинг также поощряет эту практику.

Наконец, уменьшение количества вещей, за которые отвечает сервер, уменьшает количество необходимой логики. Это, в свою очередь, обеспечивает лучшую масштабируемость и производительность.

Ограничение # 2: безгражданство

Следующим важным ограничением, предложенным REST, является безгражданство.

Вообще говоря, основная цель службы без отслеживания состояния - сделать входящие запросы самодостаточными и выполнять их в полной изоляции.

Каждый запрос должен содержать всю информацию, которая может потребоваться серверу для его правильной обработки и ответа. Другими словами, серверу не нужно использовать информацию из предыдущих запросов. Таким образом, ответственность за поддержание состояния приложения клиента перекладывается на самого клиента.

Чтобы понять это, рассмотрим очень простой веб-сервис, отвечающий за поисковые запросы пользователя. Точное представление искомого объекта не имеет значения. Важно то, что вместо того, чтобы возвращать сотни результатов поиска за один раз, сервер использует разбиение на страницы: возвращая за раз только 10 результатов из произвольно большого набора результатов.

В традиционной модели разработки с отслеживанием состояния сервер может быть спроектирован таким образом, чтобы он отслеживал всех своих клиентов, а также все страницы, к которым они уже обращались.

Итак, когда поступает запрос на новую страницу, сервер может найти клиента в своей системе и определить самую последнюю полученную страницу.

Затем сервер может перейти к ответу следующей страницей и обновить свою систему, чтобы отразить это. Это продолжается, пока клиент продолжает навигацию по набору результатов.

В альтернативном подходе без сохранения состояния ответственность за поддержание состояния децентрализована и перекладывается на клиента. Затем клиент должен указать фактические номера страниц желаемого результата, а не запрашивать следующую страницу. Например:

GET http://my-awesome-web-service.com/pages/1
GET http://my-awesome-web-service.com/pages/3

Подход без сохранения состояния дает несколько основных преимуществ. Во-первых, отслеживание состояния клиентов становится все более сложной задачей для сервера по мере увеличения количества клиентов.

Во-вторых, что более важно, услуга без отслеживания состояния также легко распространяется. Если сервер отвечает за поддержание информации о состоянии приложения, то также обязательно, чтобы будущие запросы направлялись на сервер, который хранит эту информацию.

Если есть сотни серверов, ответственных за обработку входящих запросов, тогда должен быть какой-то механизм, гарантирующий, что запросы от конкретного клиента всегда заканчиваются на конкретном экземпляре сервера.

В случае отказа экземпляра сервера вся информация о состоянии клиента, которая хранилась на этом сервере, уходит вместе с ним.

Конечно, вы можете придумать архитектуру, в которой экземпляры серверов могут обмениваться данными между собой. Но это добавляет немного сложности.

Служба без сохранения состояния, напротив, значительно упрощает добавление и удаление экземпляров сервера на разовой основе. Затем вы можете дополнительно сбалансировать нагрузку между ними по мере необходимости.

Поскольку серверы не зависят от входящих запросов, масштабирование - это просто вопрос добавления дополнительных серверов к балансировщику нагрузки. Точно так же отключение серверов - намеренное или иное - не влияет на надежность службы.

Конечно, за эту простоту приходится платить. Прикрепление клиентом идентичных данных к каждому запросу является потенциальным источником избыточности. Пропускная способность платная, поэтому любая переданная дополнительная информация увеличивает накладные расходы.

Ограничение № 3: Кэш

Третье ограничение - это явная кэшируемость. Идея состоит в том, чтобы пометить сообщения, возвращаемые службой, как явно кэшируемые или некэшируемые. Если они кэшируются, сервер должен определить продолжительность, в течение которой ответ действителен.

Если у клиента есть доступ к действительному кэшированному ответу на данный запрос, он избегает повторения того же запроса. Вместо этого он использует свою кэшированную копию. Это помогает облегчить часть работы сервера и, таким образом, способствует масштабируемости и производительности.

Это форма оптимистической репликации, также известной как ленивая репликация -, когда служба не пытается гарантировать 100% согласованность между собой и своими клиентами, за исключением случаев, когда это абсолютно критично. Вместо этого он приносит эту жертву в обмен на улучшение воспринимаемой производительности.

Например, API, соответствующий платформе для ведения блогов, может сделать список сообщений блога кэшируемым на пару минут, если знает, что частота, с которой люди пытаются получить доступ к сообщениям, намного превышает частоту с которыми создаются новые сообщения. В результате пользователи могут иногда получать устаревшие данные, но система в целом работает лучше.

Конечно, кешируемость ресурса и его продолжительность не универсальны и требуют некоторого рассмотрения. Если вы выбрали неверный вариант, это может расстроить ваших пользователей.

Веб-службы обычно достигают кэшируемости с помощью стандартного заголовка Cache-Control. Иногда они делают это вместе с другими заголовками, указанными в HTTP.

Заголовок Cache-Control фактически служит переключателем, определяющим, должен ли браузер кэшировать рассматриваемый ответ.

Ресурсы, помеченные как частные, кэшируются только клиентом и поэтому ограничены только этим одним клиентом.

Ресурсы, помеченные как общедоступные, с другой стороны, могут кэшироваться одним или несколькими промежуточными прокси-серверами между службой и клиентом.

В результате эти ресурсы потенциально могут обслуживаться множеством пользователей. В качестве альтернативы можно передать аргумент no-cache и полностью остановить кеширование ресурса.

Вот как выглядит один из этих заголовков Cache-Control:

Cache-Control: public;max-age=3431901

Заголовок также позволяет указать продолжительность, в течение которой ресурс действителен. Это позволяет клиенту узнать, когда ему следует прекратить использование своей кэшированной копии и запросить новую копию.

Вот логика этого:

Помимо этого, HTTP также имеет механизмы для выполнения так называемого условного запроса . Здесь цель состоит в том, чтобы сервер возвращал определенные ресурсы клиенту только при соблюдении определенных условий .

Предполагая, что у клиента есть сохраненная копия ресурса в кэше, он может сделать запрос к серверу, чтобы определить, есть ли обновленная копия того же самого ресурса. Если он есть, сервер возвращает новую копию. В противном случае он говорит клиенту продолжать использовать его локальную копию.

Это помогает предотвратить избыточную передачу данных по сети, а также обеспечивает постоянный доступ клиента к свежим данным.

HTTP позволяет добиться этого двумя способами:

Подход №1 к кэшированию: If-Modified-Since / Last-Modified

Наряду с каждым ответом, который сервер отправляет обратно, он может добавить временную метку Last-Modified. Это указывает, когда ресурс был в последний раз изменен.

Когда клиенту потребуется снова запросить ресурс в будущем, он отправляет запрос на сервер, как обычно, но с соответствующим заголовком If-Modified-Since. Это говорит серверу вернуть новую копию ресурса, если таковая существует.

В противном случае сервер возвращает код состояния 304, который предписывает клиенту продолжать использовать уже имеющуюся копию.

Подход №2: если-None-Match / ETag

Эта схема работает аналогично предыдущей, за исключением способа идентификации ресурсов. Вместо использования временных меток сервер отправляет с каждым ответом уникальный хэш, объясняющий состояние ресурса в этот момент времени (известный как ETag).

Для будущих запросов клиент отправляет на сервер соответствующий ETag. Если существует ресурс с таким же ETag, сервер сообщает клиенту, что нужно продолжать использовать кэшированную копию. В противном случае сервер отправляет клиенту новый.

Кеширование сложно. По мере того, как ваш сервис начинает добавлять новых пользователей, вы захотите узнать больше о кешировании и о том, как его можно использовать в своих интересах.

Ограничение №4: Единый интерфейс

Унифицированный интерфейс (или Унифицированный контракт) сообщает службе RESTful, что обслуживать, в виде документа, изображения, невиртуального объекта. , так далее.

Однако REST не диктует способ взаимодействия с этими ресурсами, если они согласованы и хорошо понятны.

В общем, прежде чем клиент сможет взаимодействовать со службой RESTful, он должен согласовать:

  1. Идентификация. Должен быть способ однозначно идентифицировать каждый ресурс, который может предложить служба.
  2. Манипуляции. Должен существовать стандартный набор операций, которые можно выполнять с любым заданным ресурсом с предсказуемыми результатами. Результаты этих операций также должны быть наглядными и понятными.

HTTP, например, использует URL-адреса для идентификации ресурсов. Он также использует несколько глаголов действия и хорошо задокументированные коды состояния для облегчения взаимодействия с ресурсом. (Для более подробного объяснения конструкций HTTP вы можете вернуться и прочитать Часть I этой серии.)

До этого момента мы считали службы RESTful строго привязанными к HTTP. Что касается веб-сервисов, это почти всегда верно.

Но теоретически REST может быть реализован по любому протоколу, который обеспечивает достойный способ достижения двух условий, которые я описал выше. По этой причине REST иногда также называют REST over HTTP, чтобы пояснить, что он используется в Интернете.

Ограничение # 5: многоуровневая система

Многоуровневая система строится на ограничении клиент-сервер, которое мы обсуждали ранее, и обеспечивает еще большее разделение задач. Общую архитектуру вашего сервиса можно разделить на отдельные уровни, каждый из которых выполняет определенную функцию.

Что еще более важно, каждый слой должен действовать независимо и взаимодействовать только со слоями, непосредственно примыкающими к нему. Это заставляет запросы распространяться предсказуемым образом, без обхода слоев.

Например, для масштабирования вы можете использовать прокси, который ведет себя как балансировщик нагрузки. Единственной целью прокси-сервера будет пересылка входящих запросов соответствующему экземпляру сервера.

С другой стороны, клиенту не нужно знать об этом разделении. Он просто продолжает делать запросы к тому же URL, не заботясь о деталях того, как запросы обрабатываются.

Точно так же в архитектуре может быть другой уровень, отвечающий за кэширование ответов, чтобы минимизировать работу, которую должен выполнять сервер.

Другой уровень может вести себя как шлюз и транслировать HTTP-запросы в другие протоколы.

Один из способов использования этого - создание FTP-сервера. Клиент будет продолжать делать запросы к тому, что он воспринимает как HTTP-сервер, в то время как у вас на самом деле есть FTP-сервер, выполняющий работу под капотом.

Подобно разграничению клиент-сервер, это многоуровневое системное ограничение сводит к минимуму риск объединения функциональных возможностей в вашем сервисе, но за счет дополнительных накладных расходов в системе.

Заключение

Подводя итог, мы рассмотрели важные ограничения, которые следует учитывать при разработке веб-сервисов RESTful. Я также хочу подчеркнуть, что, хотя это технически жесткие требования, которым должна соответствовать служба, чтобы считаться RESTful, на практике это не всегда происходит.

Создание реальных сервисов - это больше для решения насущных проблем, чем для соответствия техническим определениям. В результате эти ограничения чаще всего используются разработчиками и архитекторами в качестве руководящих указаний, которые затем решают, каким правилам следовать в их усилиях по достижению своих собственных конкретных целей.

Отсюда произошли термины частично успокаивающий и полностью успокаивающий. И действительно, большинство сервисов, с которыми вы сталкиваетесь в Интернете, технически не являются полностью RESTful.

В следующей, заключительной, части этой серии статей я буду обсуждать принципы HATEOAS, а также модель зрелости Ричардсона. Это обеспечивает чуть более количественный подход к определению того, насколько RESTful является на самом деле веб-сервисом. Найдите его здесь!

Я надеюсь, что это было полезным введением в то, что входит в создание приложения RESTful. Понимание принципов REST обязательно поможет вам, когда вы работаете с большим количеством сторонних API. Или даже когда вы создаете свои собственные приложения в Интернете, на мобильном устройстве или где-либо еще.

В качестве бонуса я также загрузил презентацию по этой теме здесь. Эта презентация заимствована из короткого выступления, которое я прочитал пару месяцев назад в моем университете, под названием Влияние архитектуры RESTful на производительность и масштабируемость приложений. Надеюсь, вы сочтете это полезным :)

Дайте мне знать в комментариях, если у вас есть какие-либо отзывы, или не стесняйтесь обращаться ко мне через мой LinkedIn.

Вот несколько ресурсов по REST для дальнейшего чтения:

Ключевые принципы архитектуры программного обеспечения - MSDN

Объяснение отдыха, презентация

Restful Web Services - Сэм Руби

WhatIsRest.com

Отдых на практике