GraphQL против REST: лучшие практики проектирования API для длительных операций
6 мая 2022 г.Этот пост в блоге был написан нашим генеральным директором и основателем [Йенсом Нусе] (https://www.linkedin.com/in/jens-neuse-706673195/). Если вам нравится этот тип контента, расскажите о себе в нашем сообществе Discord.
Недавно я прочитал статью, в которой автор заявил, что GraphQL «не подходит для длительных операций». Я хотел бы показать, что GraphQL вполне можно использовать для длительных операций.
Мы также рассмотрим, как такую проблему можно решить с помощью традиционного REST API, и сравним два подхода. В конечном итоге мы увидим, что схема GraphQL значительно упрощает рассуждения о длительных операциях с точки зрения разработчика. С другой стороны, подход REST намного проще в реализации.
Поскольку это не маркетинговый пост, а чисто технический, несколько слов о том, что мы делаем:
WunderGraph помещает слой JSON RPC перед всеми вашими API (GraphQL, REST, базы данных и т. д.).
В сочетании с созданным клиентом мы делаем использование API максимально простым, безопасным и производительным.
Как будто все ваши сервисы становятся единым сервером GraphQL, защищенным уровнем JSON RPC.
Теперь давайте сосредоточимся на дизайне схемы.
Давайте теперь воспользуемся примером, чтобы проиллюстрировать и сравнить два подхода к разработке API для длительных операций.
Что такое длительная операция?
Давайте представим, что мы создаем SaaS, который использует машинное обучение для определения тональности сообщения в блоге. Вы отправите ссылку на сообщение в блоге, и служба проанализирует настроение сообщения и вернет его вам. Эта операция может занять несколько секунд или даже минуту.
С вашей точки зрения, когда вы нажимаете кнопку в Интернете, как долго вы начинаете нервничать, наблюдая за индикатором выполнения? Вероятно, вы позволите службе работать несколько секунд, если понимаете сложность задачи. Однако мы привыкли видеть некоторый прогресс менее чем через 5 секунд, по крайней мере, когда мы используем настольный браузер.
Давайте просто предположим, что наша служба «анализ настроений» занимает более 5 секунд.
Если мы не можем ответить в течение 5 секунд, как мы можем обеспечить хорошее взаимодействие с пользователем?
Если есть просто индикатор загрузки, и мы ждем завершения запроса, пользователь может отменить запрос в любое время, закрыть браузер или даже просто покинуть страницу.
Они также могут просто нажать escape, чтобы отменить запрос и повторить попытку. Что нам нужно, так это API, который понимает понятие длительной операции. Вместо того, чтобы просто вызывать эту длительную операцию и ждать синхронного ответа, мы должны разработать асинхронный API, который позволяет легко отслеживать ход операции и даже ее отмену.
Разработка синхронного REST API для длительных операций
Если бы мы разработали наш API как синхронный API, он, вероятно, выглядел бы так:
``` оболочка
curl -X POST -H "Тип контента: application/json" -d '{"url": "https://www.wundergraph.com/blog/long_running_operations"}' http://localhost:3000/api/ v1/analyze_sentiment
Вот как может выглядеть ответ:
```json
"статус": "успех",
"данные": {
"url": "https://www.wundergraph.com/blog/long_running_operations",
"настроение": "положительное"
Однако, как мы уже говорили ранее, эта операция может занять вечность, и пользователь может ее отменить. Лучшим подходом было бы разработать наш API как асинхронный API.
Разработка асинхронного REST API для длительных операций
Давайте теперь превратим синхронный API в асинхронный API. Вместо того, чтобы возвращать ответ немедленно, мы должны вернуть ответ с уникальным идентификатором, чтобы клиент мог запросить результат у сервера.
Правильный способ разработки такого API — вернуть код состояния 202 Accepted.
Запрос получен, но еще не обработан. Это ни к чему не обязывает, поскольку в HTTP нет способа отправить асинхронный ответ позже, указывающий результат запроса. Он предназначен для случаев, когда другой процесс или сервер обрабатывает запрос, или для пакетной обработки.
Источник: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
Итак, в этом случае наш ответ API может выглядеть так:
Это будет ответ с кодом состояния 202 и следующим телом.
```json
"данные": {
"url": "https://www.wundergraph.com/blog/long_running_operations",
"идентификатор": 1
"ссылки": [
"отн": "статус",
"href": "http://localhost:3000/api/v1/analyze_sentiment/1/status"
"отн": "отменить",
"href": "http://localhost:3000/api/v1/analyze_sentiment/1/cancel"
Вместо немедленного возврата результата мы возвращаем список ссылок, чтобы вызывающий API мог получить текущий статус задания или отменить его. Затем клиент может использовать следующую команду для получения статуса задания:
```машинопись
curl -X ПОЛУЧИТЬ http://localhost:3000/api/v1/analyze_sentiment/1/status
Отлично, мы сделали наш API асинхронным.
Далее мы рассмотрим, как можно использовать GraphQL для разработки подобного API.
Разработка асинхронного API GraphQL для длительных операций
Подобно REST, мы могли бы реализовать асинхронный API для опроса сервера о состоянии задания с помощью GraphQL. Однако GraphQL поддерживает не только запросы и мутации, но и подписки. Это означает, что у нас есть лучший способ разработки нашего API, не заставляющий клиента опрашивать сервер.
Вот наша схема GraphQL:
```json
введите Работа {
я сделал!
URL: Строка!
Статус: Статус!
настроение: настроение
перечисление Статус {
в очереди
обработка
законченный
отменен
неуспешный
enum Настроение {
положительный
отрицательный
нейтральный
введите запрос {
jobStatus(id: ID!): Работа
тип Мутация {
createJob(url: String!): Работа
cancelJob(id: ID!): Работа
введите подписку {
jobStatus(id: ID!): Работа
Создание задания будет выглядеть так:
```json
мутация ($url: строка!) {
createJob(url: $url) {
я бы
URL
статус
Как только мы получим идентификатор, мы можем подписаться на изменения работы:
```json
подписка ($id: ID!) {
Статус работы (идентификатор: $ id) {
я бы
URL
статус
настроение
Это хорошее начало, но мы можем еще улучшить эту схему. В его текущем состоянии мы должны сделать «настроение» обнуляемым, потому что поле будет иметь значение только в том случае, если задание завершено.
Давайте сделаем наш API более интуитивным:
```json
работа интерфейса {
я сделал!
URL: Строка!
type SuccessfulJob реализует Job {
я сделал!
URL: Строка!
настроение: настроение!
тип QueuedJob реализует Job {
я сделал!
URL: Строка!
тип FailedJob реализует Job {
я сделал!
URL: Строка!
причина: Строка!
type CancelledJob реализует Job {
я сделал!
URL: Строка!
Время: Время!
enum Настроение {
положительный
отрицательный
нейтральный
введите запрос {
jobStatus(id: ID!): Работа
тип Мутация {
createJob(url: String!): Работа
cancelJob(id: ID!): Работа
введите подписку {
jobStatus(id: ID!): Работа
Превращение задания в интерфейс делает наш API более явным.
Теперь мы можем подписаться на статус задания, используя следующую подписку:
```json
подписка ($id: ID!) {
Статус работы (идентификатор: $ id) {
__имя_типа
... на успешном задании {
я бы
URL
чувство
... в поставленной в очередь работе {
я бы
URL
... при неудачном задании {
я бы
URL
причина
... на отмененном задании {
я бы
URL
время
Только если в поле __typename
установлено значение «SuccessfulJob», будет возвращено поле sentiment
.
Сравнение REST и GraphQL для длительных операций
Как видно из приведенных выше примеров, для разработки асинхронных API можно использовать как REST, так и GraphQL. Давайте теперь начнем обсуждение плюсов и минусов каждого подхода.
Для начала позвольте мне сказать, что должно быть совершенно ясно, что оба подхода к асинхронным API лучше, чем их синхронные аналоги. Независимо от вашего выбора использования REST или GraphQL, когда выполнение операции занимает более нескольких секунд, я всегда рекомендую вам разрабатывать свой API асинхронным способом. Теперь давайте рассмотрим крошечные детали, которые имеют значение.
Элементы управления Hypermedia и определения типов GraphQL
Одним из огромных преимуществ подхода REST является то, что все является ресурсом, и мы можем использовать элементы управления гипермедиа. Позвольте мне перевести этот «жаргон» на простые слова: одна из основных концепций REST API заключается в том, что к каждой «вещи» можно получить доступ через уникальный URL-адрес. Если вы отправите задание в API, вы получите URL-адрес, который можно использовать для проверки состояния задания. Для сравнения, GraphQL имеет только одну конечную точку. Если вы отправляете задание через мутацию GraphQL, вы возвращаете идентификатор типа «ID!».
Если вы хотите проверить статус задания, вы должны использовать идентификатор в качестве аргумента в правильном корневом поле типа запроса или подписки. Откуда вы как разработчик знаете взаимосвязь между идентификатором задания и корневыми полями типа запроса или подписки? К сожалению, нет!
Если вы хотите быть вежливым при разработке схемы GraphQL, вы можете поместить эту информацию в описание полей. Однако спецификация GraphQL не позволяет нам делать эти «ссылки» явными, как в REST. Это же правило распространяется и на отмену работы.
С помощью REST API вы можете вернуть URL-адрес клиенту, который можно вызвать для отмены задания.
С GraphQL вы должны знать, что вам нужно использовать мутацию cancelJob
, чтобы отменить задание и передать идентификатор в качестве аргумента.
Это может показаться немного преувеличением, поскольку мы используем очень маленькую схему, но представьте, если бы у нас было несколько сотен корневых полей для типов Query и Mutation. Может стать очень сложно найти правильное корневое поле для использования.
API-интерфейсы REST могут иметь схему, API-интерфейсы GraphQL должны иметь схему
Отсутствие ресурсов и уникальных URL-адресов кажется слабостью GraphQL. Однако мы можем привести аргумент и наоборот. Можно возвращать ссылки с действиями в REST API. Тем не менее, не очевидно, какие ссылки мы получаем в ответ на отправку задания. Кроме того, мы также не знаем, например. следует ли вызывать URL-адрес отмены с помощью POST или GET. Или, может быть, мы должны просто УДАЛИТЬ задание? Для этого существуют дополнительные инструменты. Одним из таких инструментов/спецификаций является [Siren] (https://github.com/kevinswiber/siren) Кевина Свибера.
Если вы хотите разрабатывать хорошие REST API, вам обязательно стоит обратить внимание на такие решения, как Siren. Тем не менее, учитывая тот факт, что API REST являются доминирующим стилем API, Siren уже более 5 лет, и только 1,2 тысячи звезд указывают на проблему. Мне кажется, что хороший (REST) дизайн API необязателен. Большинство разработчиков создают простые API-интерфейсы в стиле CRUD вместо того, чтобы использовать возможности Hypermedia.
С другой стороны, GraphQL не позволяет создавать API-интерфейсы Hypermedia из-за нехватки ресурсов. Однако схема является обязательной в GraphQL, что вынуждает разработчиков делать свои API-интерфейсы в стиле CRUD более явными и типобезопасными. По моему личному мнению, API-интерфейсы Hypermedia намного мощнее, чем API-интерфейсы в стиле CRUD, но эта мощь имеет свою цену и добавляет сложности. Именно эта сложность делает GraphQL лучшим выбором для большинства разработчиков.
Как разработчик REST API, вы «можете» использовать Siren, но большинству разработчиков все равно. Как разработчик API-интерфейсов GraphQL, вы «должны» иметь схему, без нее не обойтись. Если вы посмотрите на вторую версию нашей схемы GraphQL, использование интерфейсов поможет нам сделать API очень явным и типобезопасным. Это не идеально, нам все еще не хватает «ссылок», но это очень хороший компромисс.
Опрос и подписки
Подписка на статус задания намного элегантнее, чем опрос статуса, это очевидно. Исходя из ментальной модели пользователя API, гораздо более интуитивно понятно подписаться на поток событий, чем запрашивать статус. Тем не менее, ничего не дается бесплатно. Чтобы иметь возможность использовать подписки, вам обычно нужно использовать веб-сокеты. Веб-сокеты, сохраняющие состояние, имеют свою цену. Я писал об этой теме подробно в другом сообщении в блоге.
Добавление WebSockets в ваш стек также означает гораздо большую сложность для серверной части API. Поддерживает ли ваш хостинг-провайдер WebSockets? Некоторые бессерверные среды не допускают длительных операций или просто отклоняют запросы HTTP Upgrade. Соединения WebSocket также масштабируются иначе, чем краткосрочные соединения HTTP. WebSockets также являются функцией HTTP 1.1, что означает, что вы не можете использовать их с HTTP/2.
Если ваш веб-сайт использует HTTP/2 для всех конечных точек, клиенты должны открыть другое TCP-соединение для WebSocket. Кроме того, WebSockets могут работать не во всех средах, например. если вы находитесь за обратным прокси-сервером или используете балансировщик нагрузки. С другой стороны, HTTP-опрос — очень простое и скучное решение. Таким образом, хотя подписки GraphQL предлагают более простую ментальную модель для пользователя API, они сопряжены с большими затратами с точки зрения реализации. Имейте в виду, что вы не обязаны использовать подписки с GraphQL. Вы по-прежнему можете использовать HTTP-опрос для определения статуса задания, используя запрос вместо подписки.
Вывод
Стили API REST и GraphQL — отличные инструменты для разработки синхронных и асинхронных API. Каждый из них имеет свои сильные и слабые стороны. GraphQL более подробно описывает схему и ее систему типов. REST, с другой стороны, может быть намного более мощным благодаря уникальным URL-адресам и элементам управления Hypermedia.
Мне лично очень нравится подход Siren. Однако отсутствие явной схемы для REST API оставляет слишком много места для интерпретации обычным разработчиком. С правильными инструментами и хорошим управлением API вы сможете создавать отличные API REST. Можно возразить, что GraphQL поставляется с большим количеством готовых функций и требует меньшего управления, но я не думаю, что это правда. Как видно из двух версий нашей схемы GraphQL, дизайн схемы GraphQL обладает большой гибкостью. Даже вторую версию схемы можно улучшить. В конце концов, я не вижу, чем одно решение намного лучше другого. Гораздо важнее приложить усилия к дизайну вашего API, чем выбирать между REST или GraphQL.
Поговорите со своими пользователями и выясните, как они хотят использовать ваш API. Используются ли они для API REST или API GraphQL? Выиграют ли они от подписки вместо веб-сокетов или предпочтут простой скучный опрос? Возможно, вам даже не придется выбирать между REST и GraphQL. Если вы можете создать отличный REST API, вы можете легко обернуть его GraphQL или наоборот. Таким образом, вы можете предложить своим пользователям два стиля API, если это принесет им пользу. Ваш вывод должен заключаться в том, что хороший дизайн API и общение с вашими пользователями намного важнее, чем выбор крутой причудливой технологии.
Мы также собираемся открыть исходный код нашей реализации очень скоро. Зарегистрируйтесь со своей электронной почтой, если хотите получать уведомления, когда она будет готова. Что вы думаете об этом подходе? Присоединяйтесь к нам в Discord и делитесь своими мыслями!
Ваше здоровье!
Оригинал