Что такое RESTful веб-сервисы?

Всемирная паутина и её неожиданный успех

Всемирная паутина, или просто интернет, оказалась невероятно успешной, хотя изначально её создавали как удобный инструмент для обмена информацией между исследовательскими лабораториями. Но всё изменилось, когда её популярность резко взлетела. По подсчётам Якоба Нильсена, с 1991 по 1997 год количество веб-сайтов увеличивалось на ошеломляющие 850% в год!

Проблемы роста

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

Рой Филдинг и REST

Одним из таких пионеров был Рой Филдинг. Он решил разобраться, что именно сделало интернет-программное обеспечение таким успешным и чего ему не хватало. В своей докторской диссертации он сформулировал шесть принципов, которые назвал REpresentational State Transfer (REST). Эти принципы, по его мнению, должны были помочь создавать системы, способные масштабироваться, быть простыми и надёжными — что крайне важно для чего-то столь огромного, как интернет.

Применение REST

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

RESTful Web Services

Возникает вопрос: если следование этим принципам REST приводит к созданию таких надёжных систем, почему бы не использовать их не только для браузеров и сайтов, но и для веб-сервисов? Это привело к появлению RESTful Web Services — веб-сервисов, которые соответствуют принципам REST и идеально подходят для масштабных систем, таких как интернет.

Шесть принципов REST

Итак, что же это за шесть принципов REST? Они помогают создавать системы, которые легко масштабируются, остаются простыми в использовании и надёжными даже при огромных нагрузках.

1. Клиент-сервер

Первое ограничение REST заключается в том, что система должна состоять из клиентов и серверов.

Роль серверов и клиентов

У серверов есть ресурсы, которые клиенты хотят использовать. Например, у сервера может быть список цен на акции (ресурс), а клиент может хотеть отображать эти цены в виде красивых графиков.

Разделение задач

Между клиентами и серверами существует чёткое разделение задач:

Преимущества разделения

Такое разделение позволяет:

Снижение сложности

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

Распространённость клиент-серверной архитектуры

Это ограничение REST, вероятно, наименее спорное, поскольку клиент-серверная архитектура сегодня настолько распространена, что мы почти забываем о существовании других подходов (например, протоколов на основе событий).

О протоколах

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

2. Без состояния

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

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

Преимущества

Рой Филдинг даже отмечал, что зависимость от сеансов на стороне сервера часто становится причиной сбоев веб-приложений и ухудшает масштабируемость.

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

3. Кэш

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

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

Протоколы, такие как SOAP, которые используют HTTP только для прохождения через брандмауэры (используя POST для всех запросов), упускают преимущества кэширования HTTP. Это снижает их производительность и частично нарушает цель использования брандмауэра.

4. Единый интерфейс

Что действительно отличает REST от других архитектурных стилей, так это Единый интерфейс, навязываемый четвёртым ограничением.

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

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

Ограничение Единого интерфейса состоит из 4 подограничений:

4.1. Идентификация ресурсов

Стиль REST сосредоточен вокруг ресурсов. Это отличает его от SOAP и других стилей RPC, которые ориентированы на процедуры (или методы).

Ресурс — это всё, что можно назвать. Это может быть статическое изображение, лента с текущими ценами акций или что-то другое.

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

Каждый ресурс в RESTful-дизайне должен быть уникально идентифицируемым с помощью URI (унифицированного идентификатора ресурса). Идентификатор должен оставаться стабильным даже при обновлении ресурса (как говорится, «Крутые URI не меняются»).

Это означает, что каждый ресурс, предоставляемый через RESTful-веб-службу, должен иметь свой собственный URI. Например:

Некоторые известные API, которые заявляют о своей RESTful-природе, не соответствуют этому требованию. Например, REST API Twitter использует RPC-подобные URI, такие как statuses/destroy/:id, как и Flickr.

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

4.2. Управление ресурсами через представления

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

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

Вместо этого сервер предоставляет представление состояния ресурса. Это может звучать сложно, но на самом деле всё просто.

Это означает, что данные ресурса (его состояние) передаются в нейтральном формате. Например, данные для веб-страницы могут храниться в базе данных, но всегда отправляются в браузер в формате HTML.

Наиболее распространённым форматом для RESTful-веб-сервисов является JSON, который используется в теле HTTP-запросов и ответов:

{
  "id": 12,
  "firstname": "Han",
  "lastname": "Solo"
}

Когда клиент хочет обновить ресурс, он:

  1. Получает представление ресурса с сервера.
  2. Обновляет представление новыми данными.
  3. Отправляет обновлённое представление на сервер.
  4. Просит сервер обновить ресурс в соответствии с новым представлением.

Преимущество такого подхода в том, что он избегает сильной связи между клиентом и сервером (как в случае с RMI в Java). Это позволяет изменять базовую реализацию сервера, не затрагивая клиентов. Кроме того, клиентам не нужно понимать базовые технологии, используемые сервером, что упрощает их задачу.

4.3. Самоописательные сообщения

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

Каждое сообщение должно иметь тип носителя (например, application/json или application/xml), который указывает получателю, как интерпретировать сообщение.

HTTP не является обязательным для RESTful-веб-сервисов, но если вы используете методы HTTP, вы должны следовать их стандартным значениям. Например:

Для URI клиентов, которые мы определили ранее, можно использовать следующие методы HTTP:

Задача Метод Путь
Создать нового клиента POST /customers
Удалить существующего клиента DELETE /customers/{id}
Получить определённого клиента GET /customers/{id}
Поиск клиентов GET /customers
Обновить существующего клиента PUT /customers/{id}

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

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

4.4. Гипермедиа как движок состояния приложения (HATEOAS)

Четвёртое и последнее подограничение в едином интерфейсе называется Гипермедиа как движок состояния приложения (HATEOAS). Это звучит сложно, но на самом деле это простая концепция.

Веб-страница — это экземпляр состояния приложения, а гипермедиа — это текст с гиперссылками. Гипермедиа управляет (является движком) состоянием приложения. Другими словами, мы нажимаем на ссылки, чтобы перейти на новые страницы (т.е. изменить состояние приложения).

Когда вы просматриваете веб-страницы, вы уже используете гипермедиа как движок состояния приложения!

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

RESTful-сервис должен работать как хороший веб-сайт: вы вводите начальный URI, а затем переходите по ссылкам, предоставленным в ответах. Вам не нужно знать ничего, кроме начального URI.

Например, в представлении клиента может быть раздел ссылок на заказы клиента:

{
  "id": 12,
  "firstname": "Han",
  "lastname": "Solo",
  "_links": {
    "self": {
      "href": "https://api.example.com/customers/12"
    },
    "orders": {
      "href": "https://api.example.com/customers/12/orders"
    }
  }
}

Служба также может предоставлять ссылки в HTTP-заголовке Link. W3C работает над стандартизацией типов отношений, что поможет пользователям API лучше понимать ссылки.

Преимущество HATEOAS в том, что пользователю API не нужно искать в документации, чтобы узнать, как найти заказы клиента. Он может исследовать API во время разработки, не обращаясь к внешней документации.

Это также означает, что пользователю не нужно жёстко кодировать URI вручную. Крейг МакКланахан (соавтор The Sun Cloud API) отмечал, что 90% дефектов клиентов были вызваны неправильно построенными URI.

Рой Филдинг не уделил много внимания HATEOAS в своей диссертации из-за нехватки времени, но позже он разъяснил детали в своём блоге.

5. Layered System (Многоуровневая система)

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

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

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

6. Code-On-Demand (Код по запросу, опционально)

Шестое и последнее ограничение является единственным опциональным в стиле REST.

Code-On-Demand означает, что сервер может расширять функциональность клиента во время выполнения, отправляя ему код для выполнения (например, Java Applets или JavaScript).

Я не слышал о RESTful-веб-сервисах, которые действительно отправляют код с сервера на клиент (после развёртывания) и выполняют его на стороне клиента, но это могло бы быть мощным способом улучшить клиент.

Преимущества простоты

Огромное преимущество REST, особенно благодаря Единому интерфейсу и отсутствию состояния, заключается в том, что клиентский код становится предельно простым в написании. Современные фреймворки и библиотеки, такие как React, Vue.js или Next.js, автоматически обрабатывают большую часть рутинных задач, если следовать REST-принципам.

Например, с использованием React и библиотеки Axios для работы с API, код для создания клиента может выглядеть так:

import axios from 'axios';

// Настройка базового URL для API
const apiClient = axios.create({
  baseURL: 'https://api.example.com/customers',
});

// Создание нового клиента
const createCustomer = async (firstName, lastName) => {
  const response = await apiClient.post('/', { firstName, lastName });
  console.log('Клиент создан:', response.data);
};

// Использование
createCustomer('Han', 'Solo');

Или, если вы используете Vue.js с Composition API:

import { ref } from 'vue';
import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.example.com/customers',
});

const createCustomer = async (firstName, lastName) => {
  const response = await apiClient.post('/', { firstName, lastName });
  console.log('Клиент создан:', response.data);
};

// Использование
createCustomer('Han', 'Solo');

Современные UI-библиотеки, такие как Material-UI или Tailwind CSS, позволяют быстро создавать красивые формы для ввода данных. Например, с React и Material-UI:

import React, { useState } from 'react';
import axios from 'axios';
import { TextField, Button } from '@mui/material';

const CustomerForm = () => {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await axios.post('https://api.example.com/customers', { firstName, lastName });
    console.log('Клиент создан!');
  };

  return (
    <form onSubmit={handleSubmit}>
      <TextField
        label="Имя"
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
      />
      <TextField
        label="Фамилия"
        value={lastName}
        onChange={(e) => setLastName(e.target.value)}
      />
      <Button type="submit" variant="contained">Создать</Button>
    </form>
  );
};

export default CustomerForm;

Таким образом, с современными инструментами разработки создание RESTful-клиентов становится быстрым и интуитивно понятным процессом.