Введение в GraphQL: преимущества и недостатки

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

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

Точная выборка данных

Важность и полезность точной выборки данных GraphQL невозможно переоценить. Благодаря GraphQL вы можете отправить запрос в свой API и получить именно тот набор данных, который вам нужен, не больше и не меньше. Это действительно точная и простая функция. Если сравнить ее с традиционным интуитивно понятным шаблоном REST, несложно будет заметить значительное улучшение в изначальном подходе к работе.

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

То есть вместо нескольких конечных точек, которые возвращают фиксированные структуры данных, сервер GraphQL предоставляет единую конечную точку и отвечает именно теми данными, что запросил клиент.

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

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

METHOD /api/:resource:/:id:

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

GET /api/artists

Но как запросить отдельного исполнителя из списка? Для этого мы должны добавить ID ресурса после конечной точки:

GET /api/artists/1

По сути, нам нужно вызвать две разные конечные точки, чтобы получить необходимые данные. А в GraphQL каждый запрос можно выполнить в одной конечной точке, причем все действия и возвращаемые данные определяются в самом запросе. Допустим, мы хотим извлечь трек определенного исполнителя и его продолжительность; в GraphQL у нас будет такой запрос:

GET /api?query={ artists(id:"1") { track, duration } }

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

Один запрос – множество ресурсов

Еще одна полезная особенность GraphQL – простота выборки всех необходимых данных одним единственным запросом. Структура серверов GraphQL позволяет декларативно извлекать данные, поскольку GraphQL предоставляет только одну конечную точку.

Допустим, пользователь хочет запросить информацию о конкретном исполнителе (его имя, идентификатор, треки и т. д.). При использовании традиционного интуитивного шаблона REST для этого потребуется как минимум два запроса к двум конечным точкам: /artists и /tracks. Однако с помощью GraphQL мы можем определить все необходимые данные в рамках одного запроса:

// the query request
artists(id: "1") {
id
name
avatarUrl
tracks(limit: 2) {
name
urlSlug
}
}

Только что мы определили один запрос GraphQL для нескольких ресурсов (artists и tracks). Этот запрос вернет только запрошенные нами ресурсы:

// the query result
{
"data": {
"artists": {
"id": "1",
"name": "Michael Jackson",
"avatarUrl": "https://artistsdb.com/artist/1",
"tracks": [
{
"name": "Heal the world",
"urlSlug": "heal-the-world"
},
{
"name": "Thriller",
"urlSlug": "thriller"
}
]
}
}
}

Как видите, мы извлекли ресурсы для двух конечных точек, /artists и /tracks, с помощью одного вызова API. Это мощная функция GraphQL. Несложно себе представить, насколько применение этой функции эффективно при работе с декларативными структурами API.

Совместимость с современными приложениями

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

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

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

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

Глубокий анализ сшивки схем с примерами вы найдете здесь.

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

Депрекация на уровне поля

Это одна из самых полезных возможностей GraphQL. Разработчики привыкли вызывать разные версии API и часто получать странные ответы. Как правило, они создают новые версии API после изменения текущих ресурсов или их структуры – следовательно, возникает потребность объявить предыдущую версию устаревшей в пользу новой версии.

Предположим, у нас есть API api.domain.com/resources/v1. В течение ближайших месяцев или лет в нем произойдут некоторые изменения, следовательно, изменятся и ресурсы или структура ресурсов. Лучше всего в такой ситуации будет зафиксировать это состояние API как api.domain.com/resources/v2, то есть как новую версию.

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

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

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

Кэширование

Кэширование – это временное хранилище часто запрашиваемых данных, благодаря которому будущие запросы на эти данные могут обслуживаться быстрее. Данные, хранящиеся в кэше, могут быть результатом более ранних вычислений или дубликатом данных, хранящихся в другом месте. Целью кэширования ответов API является, прежде всего, более быстрое получение ответа на будущие запросы. В отличие от GraphQL, кэширование встроено в спецификацию HTTP, которую могут использовать RESTful API.

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

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

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

Производительность запросов

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

Например, пользователь определяет запрос, который собирает список всех пользователей, прокомментировавших треки конкретного исполнителя. Такой запрос будет выглядеть примерно так:

artist(id: '1') {
id
name
tracks {
id
title
comments {
text
date
user {
id
name
}
}
}
}

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

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

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

Несоответствие данных

При сборке бэкенда на GraphQL  уже приводили такой пример: часто база данных и API GraphQL имеют похожие, но все же разные схемы, которые переводятся в разные структуры документа. То есть поле track при извлечении из базы данных будет иметь на клиенте свойство trackId, а при извлечении из API – свойство track. В итоге возникает несоответствие данных между клиентом и сервером.

Для примера попробуем извлечь имя исполнителя определенного трека на стороне клиента, и это будет выглядеть так:

const getArtistNameInClient = track => {
return artist.user.name
}

Однако точно такой же запрос на серверной стороне вернет другой результат:

const getArtistNameInServer = track => {
const trackArtist = Users.findOne(track.userId)
return trackArtist.name
}

К счастью, этот недостаток можно исправить. Оказывается, вы можете просто выполнять запросы GraphQL между серверами. Как это делается? Для этого можно передать свою исполняемую схему GraphQL в функцию GraphQL вместе с вашим запросом:

const result = await graphql(executableSchema, query, {}, context, variables);

Как объясняется в этом блоге, важно рассматривать GraphQL не просто как чистый протокол клиент-сервер. GraphQL можно использовать для запроса данных в любой ситуации, в том числе между клиентами (с помощью Apollo Link State) или даже во время статической сборки (с помощью Gatsby).

Повторение схем

При сборке бэкенда с помощью GraphQL вы, скорее всего, не сможете избежать дублирования и повторения кода, особенно когда речь идет о схемах. Как минимум, вам нужна одна схема для БД, вторая – для конечной точки GraphQL,и обе они содержат похожий, но не совсем идентичный код.

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

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

  • PostGraphile генерирует схему GraphQL на основе вашей базы данных PostgreSQL.
  • Prisma  помогает генерировать типы для ваших запросов и мутаций.

Заключение

GraphQL – отличная современная технология, но вы должны оценить ее преимущества и недостатки, прежде чем принимать дорогостоящие и важные архитектурные решения. Некоторые API (в частности те, у которых очень мало объектов  и связей между ними, такие как аналитические API) не очень эффективны в работе с GraphQL. Однако GraphQL отлично подходит для приложений с множеством различных доменных объектов (например, это приложения электронной коммерции, в которых есть пользователи, заказы, платежи и т. д).

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

Tags: , ,

Добавить комментарий