Основы работы с запросами в GraphQL

В этом руководстве мы подробно рассмотрим запросы в 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:

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