Перейти к содержанию

Запросы и мутации

На этой странице Вы узнаете подробно о запросах к GraphQL серверу.

Поля

По простому, GraphQL запрашивает определенные поля у объектов. Давайте посмотрим на очень простой запрос и результат, который мы получим:

{
  hero {
    name
  }
}
{
   "data": {
     "hero": {
       "name": "R2-D2"
     }
   }
 }

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

Поле name возвращает тип String в данном случае имя главного героя Star Wars, "R2-D2".

Да, и еще одно - этот запрос является интерактивным. Это означает, что Вы можете изменить его, как Вам нравится, и увидеть новый результат. Попробуйте добавить поле appearsIn в объект hero в запросе, и увидите новый результат.

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

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}
{
   "data": {
     "hero": {
       "name": "R2-D2",
       "friends": [
         {
           "name": "Luke Skywalker"
         },
         {
           "name": "Han Solo"
         },
         {
           "name": "Leia Organa"
         }
       ]
     }
   }
 }

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

Аргументы

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

{
  human(id: "1000") {
    name
    height
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

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

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

Аргументы могут быть разных типов. В приведенном выше примере, мы использовали тип Enumeration, который представляет одно значение из нескольких вариантов (в данном случае единицы измерения длины, как METER или FOOT). GraphQL поставляется со стандартным набором типов, но GraphQL сервер также может объявить свой собственный пользовательский тип, поскольку он может быть сериализован в Вашем ответе.

Подробнее о типах системы GraphQL.

Псевдонимы

Если Вы предельно внимательны, Вы, возможно, заметили, что, хоть поля объекта-результата соответствуют имени поля в запросе, но они не включают аргументы. Вы не можете сделать запрос непосредственно для того же самого поля с разными аргументами. Вот почему Вам нужны псевдонимы - они позволяют Вам переименовывать результат поля во что Вам угодно.

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

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

Фрагменты

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

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

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

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

Переменные

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

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

Когда мы начинаем работать с переменными, нам нужно сделать три вещи:

  • Замените статическое значение в запросе переменной $variableName
  • Объявить $variableName в качестве одной из переменных, принимаемых запросом
  • Передать variableName: value в отдельном транспортном словаре (обычно JSON) переменных

Вот как это выглядит все вместе:

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
{
  "episode": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

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

Определения переменных

Определения переменных - это часть, которая выглядит ($episode: Episode) в указанном выше запросе. Он работает так же, как определения аргументов для функции на типизированном языке. В нем перечислены все переменные с префиксом $, за которыми следует их тип, в этом случае Episode.

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

Определения переменных могут быть необязательными или обязательными. В случае выше, так как нет ! рядом с типом Episode, это необязательно. Но если в поле, в которое передается переменная, требуется аргумент non-null, то переменная должна быть обязательной.

Чтобы узнать больше о синтаксисе этих определений переменных, полезно изучить язык схемы GraphQL. Язык схемы подробно объясняется на странице схемы.

Название операции

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

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

Директивы

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

Давайте создайте запрос для такого компонента:

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}
{
  "episode": "JEDI",
  "withFriends": false
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

Попробуйте изменить перечисленные выше переменные, передав true для withFriends и посмотреть, как изменится результат.

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

  • @include(if: Boolean) Только включает это поле в результат, если аргумент имеет значение true.
  • @skip(if: Boolean) Пропускает это поле, если аргумент имеет значение true.

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

Мутации

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

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

Как и в запросах, если mutation поле возвращает тип объекта, Вы можете запросить вложенные поля. Это может быть полезно для извлечения нового состояния объекта после обновления. Давайте посмотрим на простой mutation пример:

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

Обратите внимание, что поле createReview возвращает поля stars и commentary только что созданного обзора. Это особенно полезно при изменении существующих данных, например, при увеличении поля, поскольку мы можем изменять и запрашивать новое значение поля с помощью одного запроса.

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

Несколько полей в мутациях

Mutation может содержать несколько полей, как и query. Кроме имени, cуществует одно важное различие между query и mutation:

Хотя поля запроса выполняются параллельно, mutation поля запускаются последовательно, один за другим.

Это означает, что если мы пошлем две mutations incrementCredits в одном запросе, первое гарантируется до завершения второго, гарантируя, что мы не закончим с самим условием гонки.

Встроенные фрагменты

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

Если вы запрашиваете поле, которое возвращает интерфейс или тип объединения, Вам нужно будет использовать встроенные фрагменты для доступа к данным на конкретном типе. Проще всего увидеть на примере:

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
{
  "ep": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

В этом запросе поле hero возвращает тип Character, который может быть либо Human, либо Droid в зависимости от episode аргумента. В прямом выборе вы можете запрашивать только поля, которые существуют на интерфейсе Character, такие как name.

Чтобы запросить поле по конкретному типу, Вам нужно использовать встроенный фрагмент с условием типа. Поскольку первый фрагмент помечен как ... on Droid, поле primaryFunction будет выполняться только в том случае, если Character, возвращенный из hero, имеет тип Droid. Аналогично полю height для типа Human.

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

Мета-поля

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

{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
    ... on Droid {
      name
    }
    ... on Starship {
      name
    }
  }
}
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}

В вышеупомянутом запросе search возвращает тип объединения, который может быть одним из трех вариантов. Невозможно отличить разные типы от клиента без поля __typename.

Сервисы GraphQL предоставляют несколько метаполей, остальная часть которых используется для Introspection system.