Основы SDL: схемы и типы GraphQL

Чаще всего схемы сервисов GraphQL задаются с помощью так называемого SDL (иногда его называют просто языком схем GraphQL). Это самостоятельный язык с очень простым, интуитивно понятным синтаксисом, который позволяет быстро определить схему. Как только вы разберетесь с различными элементами синтаксиса SDL, вы сможете писать схемы на раз-два. Данный мануал поможет вам в этом.

Что такое схемы GraphQL

Вот так выглядит базовое определение схемы GraphQL для простого приложения, которое помогает составлять списки дел.

# Типы приоритетности
enum Priority {
  LOW
  MEDIUM
  HIGH
}

# Главный тип
type Todo {
  id: ID!
  name: String!
  description: String
  priority: Priority!
}

type Query {
  # извлекает одно дело из списка
  todo(id: ID!): Todo
  # извлекает весь список дел
  allTodos: [Todo!]!
}

type Mutation {
  addTodo(name: String!, priority: Priority = LOW): Todo!
  removeTodo(id: ID!): Todo!
}

schema {
  query: Query
  mutation: Mutation
}

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

Типы объектов

Типы объектов GraphQL определяются с помощью ключевого слова type и по соглашению начинаются с заглавной буквы. Они задают вид объекта и поля, представленные под ним. Каждое поле в объекте может быть разрешено либо к другим типам объектов, либо к скалярным типам. Скалярные типы определяют фактические данные и представляют собой листья запроса.

В приведенном выше определении схемы есть тип объекта Todo, а также корневые типы Query и Mutation. Во всех схемах GraphQL обязательно использовать только корневой тип Query, но чаще всего корневой тип Mutation также присутствует, если сервис позволяет обновлять, добавлять или удалять данные. Кроме того, существует корневой тип Subscription – он предназначен для определения операций, на которые может подписаться клиент.

Встроенные скалярные типы

В GraphQL есть 5 встроенных скалярных типов: Int, Float, String, Boolean и ID. Скалярные типы, в отличие от типов объектов, указывают на фактические данные. Тип ID преобразуется в строку, но требует, чтобы значение было уникальным.

Перечисляемые типы

Перечисляемые типы позволяют определить конкретное подмножество возможных значений для того или иного типа. В предыдущем примере есть перечисляемый тип Priority, он может принимать значения LOW, MEDIUM или HIGH – значит, все остальные значения приведут к ошибке. Для предоставления значения перечисляемых типов на клиенте используются строки.

Модификаторы

Как вы можете видеть из приведенного выше примера, модификаторы можно использовать для типов, в которые преобразуются поля. Это делается с помощью таких символов, как ! и […]. Давайте рассмотрим модификаторы на примере скалярного типа String:

  • String: строка, допускающая значение NULL (разрешенное значение может быть нулевым)
  • String!: Строка, не допускающая значения NULL (если разрешенное значение равно NULL, возникает ошибка)
  • [String]: Нулируемый список строковых значений, допускающих значение NULL. Нулевым может быть как все значение, так и отдельные элементы списка.
  • [String!]: Нулируемый список строковых значений, не допускающих значения NULL. Это значит, что все значение может быть нулевым, но определенные элементы списка – не могут.
  • [String!]!: Не-нулируемый список строковых значений, не допускающих значения NULL. Это значит, что ни ни все значение, ни отдельные элементы не могут быть нулевыми. При этом пустой список ([]) будет действителен, потому что все значение не равно нулю и в нем нет отдельных нулевых значений.

Комментарии

Комментарии добавляются с помощью символа #. GraphQL поддерживает только однострочные комментарии.

Пользовательские скалярные типы

Также в GraphQL можно определять пользовательские скалярные типы. Для этого существует такой синтаксис:

scalar DateTime

При этом сервис GraphQL должен определить, как сериализовать и проверять пользовательский тип.

Объединенные типы

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

# ...

union Vehicule = Car | Boat | Plane

type Query {
  getVehicule(id: ID!): Vehicule!
}

С объединенными типами на клиенте следует использовать встроенные фрагменты для выбора желаемых полей (в зависимости от того, какой подтип разрешается):

query {
  getVehicule {
    ...on Car {
      year
    }
    ...on Boat {
      color
    }
    ...on Plane {
      seating
    }
  }
}

Интерфейсы

Интерфейсы чем-то похожи на объединенные типы, но они позволяют совместно использовать некоторые поля сразу нескольким типам объектов:

interface Vehicule {
  color: String
  make: String
  speed: Int
}

type Car implements Vehicule {
  color: String
  make: String
  speed: Int
  model: String
}

# ...

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

graphql {
  getVehicule {
    color
    make
    ...on Car {
      model
    }
  }
}

Типы ввода

Когда запрос или мутация (query или mutation) ожидают нескольких аргументов, гораздо проще определить тип ввода, где каждое поле представляет аргумент:

# ...

input NewTodoInput {
  name: String!
  priority: Priority = LOW
}

type Mutation {
  addTodo(newTodoInput: NewTodoInput!): Todo!
  removeTodo(id: ID!): Todo!
}

Документация схемы

Также в GraphiQL существует синтаксис для добавления удобочитаемой документации по типам и полям. Документация может пригодиться при использовании таких инструментов, как GraphiQL или GraphQL Playground.

Давайте вернемся к нашему первому примеру – к схеме приложения для хранения списков дел, – и добавим в схему документацию типов и некоторых полей:

"""
Priority level
"""
enum Priority {
  LOW
  MEDIUM
  HIGH
}

type Todo {
  id: ID!
  name: String!
  """
  Useful description for todo item
  """
  description: String
  priority: Priority!
}

"""
Queries available on the todo app service
"""
type Query {
  """
  Get one todo item
  """
  todo(id: ID!): Todo
  """
  List of all todo items
  """
  allTodos: [Todo!]!
}

type Mutation {
  addTodo(
    "Name for the todo item"
    name: String!
    "Priority levl of todo item"
    priority: Priority = LOW): Todo!
  removeTodo(id: ID!): Todo!
}

schema {
  query: Query
  mutation: Mutation
}

Как видите, документация для типов или полей добавляется с помощью синтаксиса *“”“ … ”“”*. В свою очередь, синтаксис *“ … ”*  используется для документации по аргументам.

Заключение

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

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

Tags:

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