Службы 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, он должен согласовать:
- Идентификация. Должен быть способ однозначно идентифицировать каждый ресурс, который может предложить служба.
- Манипуляции. Должен существовать стандартный набор операций, которые можно выполнять с любым заданным ресурсом с предсказуемыми результатами. Результаты этих операций также должны быть наглядными и понятными.
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
Объяснение отдыха, презентация