Основы работы с запросами в GraphQL
Development | Комментировать запись
В этом руководстве мы подробно рассмотрим запросы в GraphQL и расскажем, как извлекать данные с сервера GraphQL. Вы узнаете, что такое поля, аргументы, псевдонимы, как выглядит основной синтаксис операций и многое другое. Ознакомившись с данным материалом, вы сможете создавать более продуманные API с помощью GraphQL.
Поля в GraphQL
Запрос — это конструкция, используемая клиентом для извлечения определенных полей с сервера. Работа GraphQL основана на получении определенных полей в объектах. Следовательно, мы не можем обсуждать запросы, не говоря при этом о полях.
Рассмотрим такую ситуацию: клиент хочет запросить имена футболистов из конечной точки API. Запрос будет структурирован следующим образом:
{ players { name } }
Типичный запрос GraphQL выглядит именно так. Запросы состоят из двух отдельных частей:
- Корневое поле (root field): объект, содержащий полезную нагрузку. В нашем примере это поле players.
- Полезная нагрузка (payload): поля, запрошенные клиентом. В данном примере это name.
Это важная часть GraphQL: так сервер понимает, какие поля запрашивает клиент, и всегда отвечает именно этими данными. Возвращаясь к нашему примеру, на свой запрос мы могли бы получить такой ответ:
{ "players": [ {"name": "Pogba"}, {"name": "Lukaku"}, {"name": "Rashford"}, {"name": "Marshal"} ] }
Поле name возвращает данные строчного типа, в этом случае – имена игроков «Манчестер Юнайтед». Однако GraphQL не ограничивается только строками: здесь могут храниться поля всех типов данных (к примеру, корневое поле player возвращает массив элементов). Узнать больше о системе типов GraphQL в можно официальной документации.
Вышеприведенный пример довольно прост – в среде производства обычно выполняются более сложные операции. Немного усложним наш пример: мы можем переопределить запрос, выбрать отдельного игрока из списка и запросить дополнительные данные о нем. Чтобы иметь возможность получить подробную информацию, нам нужен способ идентифицировать этого игрока. В GraphQL для этого существуют аргументы.
Аргументы в GraphQL
Запросы GraphQL позволяют передавать аргументы в поля и вложенные объекты. Вы можете передавать аргументы каждому полю и каждому вложенному объекту в запросе, чтобы еще больше конкретизировать запрос и сделать несколько выборок. Аргументы служат той же цели, что и обычные параметры запроса или сегменты URL в REST API. Передавая аргументы их в поля запроса, мы дополнительно указываем, как сервер должен реагировать на наш запрос.
Вернемся к предыдущему примеру и усложним наш запрос. Давайте попробуем извлечь данные о об экипировке конкретного игрока: размер его рубашки или размер обуви (shirt size или shoe size). Во-первых, для этого нам нужно указать интересующего нас игрока, передав аргумент id (он идентифицирует игрока в списке), а затем определить нужные нам поля в полезной нагрузке запроса:
{ player(id : "Pogba") { name, kit { shirtSize, bootSize } } }
Здесь мы запросили соответствующие поля об игроке Pogba (его определяет аргумент id, который мы передали в запрос). Как и в случае с полями, здесь нет ограничений по типу данных – аргументы тоже могут быть разных типов. Результат запроса будет выглядеть следующим образом:
{ "player": { "name": "Pogba", "kit": [ { "shirtSize": "large", "shoeSize": "medium" } ] } }
Одна из потенциальных проблем в подобной ситуации заключается в том, что запросы GraphQL для отдельных элементов и списков элементов выглядят почти одинаково. Помните: мы всегда знаем, чего ожидать от GraphQL, если это определено в схеме.
Более того, запросы GraphQL являются интерактивными, поэтому вы можете по желанию добавлять дополнительные поля в объект корневого поля. Таким образом, у клиента есть возможность в рамках одного запроса получить столько данных, сколько ему нужно.
А что произойдет, если мы захотим извлечь одинаковые поля сразу для двух игроков? Для этого и придумали псевдонимы.
Псевдонимы в GraphQL
Если вы внимательно посмотрите на наш последний пример, вы заметите, что поля объекта результата:
// результат... "player": { "name": "Pogba", "kit": [ { "shirtSize": "large", "shoeSize": "medium" } ] }
совпадают с полями запроса:
// поля в запросе и в результате совпадают player(id : "Pogba") { name, kit { shirtSize, bootSize } }
Но без аргумента:
(id : "Pogba")
Поэтому мы не можем напрямую запросить одного и того же игрока с разными аргументами. То есть невозможно сделать вот такой запрос:
{ player(id : "Pogba") { name, kit { shirtSize, bootSize } } player(id : "Lukaku") { name, kit { shirtSize, bootSize } } }
Однако можно использовать псевдонимы. Они позволяют переименовывать результат поля так, как вы захотите. Чтобы запросить информацию об экипировке двух игроков, мы должны определить следующий запрос:
{ player1: player(id: "Pogba") { name, kit { shirtSize, shoeSize } } player2: player(id: "Lukaku") { name, kit { shirtSize, shoeSize } } }
Здесь есть два поля player, и они конфликтуют. Но мы можем присвоить им разные имена – player1 и player2, – и получить оба результата в одном запросе:
{ "data": { "player1": { "name": "Pogba", "kit": [ { "shirtSize": "large", "shoeSize": "medium" } ] }, "player2": { "name": "Lukaku", "kit": [ { "shirtSize": "extralarge", "shoeSize": "large" } ] } } }
Используя псевдонимы, мы смогли запросить одно и то же поле с разными аргументами и получили ожидаемый ответ.
Синтаксис операций
До сих пор мы использовали сокращенный синтаксис, в котором не требовалось явно определять ни имя, ни тип операции. Однако в производственной среде рекомендуется использовать имена и типы операций – это поможет сделать кодовую базу более конкретной и однозначной. Также такой подход упрощает отладку запросов в случае ошибки.
Синтаксис операций состоит из двух основных частей:
- Тип операции сообщает GraphQL, какую именно операцию вы хотите выполнить. Это может быть запрос, мутация или подписка
- Имя операции поможет идентифицировать операцию, которую вы пытаетесь выполнить. Это имя может быть любым.
Теперь мы попробуем переписать наш предыдущий пример и добавить тип и имя операции. Итог выглядит следующим образом:
query PlayerDetails{ player(id : "Pogba") { name, kit { shirtSize, bootSize } } }
В этом примере query — это тип операции, а PlayerDetails — ее имя.
Переменные
Ранее мы передавали все наши аргументы непосредственно в строку запроса. Однако в большинстве случаев аргументы являются динамическими. Например, предположим, игрок, информацию о котором клиент хочет получить, поступает из формы ввода или выпадающего меню. Аргумент, который мы передаем в строку запроса, должен быть динамическим, а для этого нам нужно использовать переменные. Именно переменные используются для выделения динамических значений из запросов и передачи их в виде отдельного словаря.
Допустим, если бы в нашем примере мы хотели сделать игроков динамическими, нам нужно было бы сохранить значение идентификатора игрока в переменной и передать его в имени операции и аргументе запроса:
query PlayerDetails ($id: String){ player (id : $id) { name, kit { shirtSize, bootSize } } }
Здесь $id: String — это определение переменной. В данном случае id — ее имя, перед которым стоит $, а String — ти данных. При построении динамических запросов мы можем избежать ручной интерполяции строк.
Фрагменты
Взгляните на наш запрос еще раз, и вы заметите, что поле player практически одинаково для обоих игроков:
name, kit { shirtSize, bootSize }
Чтобы сделать запрос более эффективным, мы можем извлечь эту часть общей логики и превратить ее в многократно используемый фрагмент. Это делается так:
{ player1: player(id: "Pogba") { ...playerKit } player2: player(id: "Lukaku") { ...playerKit } } fragment playerKit on player { name, kit { shirtSize, shoeSize } }
Возможность извлекать фрагмент общего кода и повторно использовать его в нескольких областях — важнейший подход, который помогает разработчикам избежать повторения в процессе разработки и даже в производстве. Если вы работаете с многоуровневой кодовой базой, вам будет гораздо удобнее повторно использовать один фрагмент в разных местах кода.
Директивы
Директивы GraphQL сообщают серверу, что следует делать с тем или иным полем при ответе на запрос. В GraphQL есть две встроенные директивы, которые помогают нам достичь этого:
- @skip: пропускает определенное поле, когда переданное ей значение истинно.
- @include: включает определенное поле, когда переданное ей значение истинно
Добавим директиву Boolean и попробуем использовать директиву @skip:
query PlayerDetails ($playerShirtDirective: Boolean!){ player(id: "Pogba") { name, kit { shirtSize @skip(if: $playerShirtDirective) bootSize } } }
Следующее, что мы сделаем, это создадим директиву playerShirtDirective в переменных запроса и установим для нее значение true:
// Query Variables { "itemIdDirective": true }
Теперь этот запрос вернет полезную нагрузку без shirtSize:
"player": { "name": "Pogba", "kit": [ { "shoeSize": "medium" } ] }
Директива @include работает как противоположность директиве @skip.
Заключение
В этой статье мы подробно рассмотрели запросы GraphQL.
Если вы хотите узнать больше о GraphQL, ознакомьтесь с официальной документацией проекта.
Читайте также: Как работают мутации и подписки в GraphQL
Tags: GraphQL