В чем смысл типа ввода в GraphQL?

90

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

Например:

type Sample {
  id: String
  name: String
}

input SampleInput {
  name: String
}

type RootMutation {
  addSample(sample: Sample): Sample  # <-- instead of it should be
  addSample(sample: SampleInput): Sample
}

Это нормально для небольших объектов, но когда у вас много объектов с 10+ свойствами в схеме, это станет обузой.

Дмитрий Душкин
источник
32
Входные объекты должны быть сериализуемыми. Поскольку выходные объекты могут содержать циклы, их нельзя повторно использовать для ввода.
Джесси Бьюкенен
1
Джесси, похоже, ответа хватит! Вы можете ответить, и я отмечаю это так.
Дмитрий Душкин
Интересно, можно ли с ним комбинировать интерфейсы
MVCDS

Ответы:

114

Из спецификации:

Тип объекта GraphQL (ObjectTypeDefinition) ... не подходит для повторного использования [в качестве ввода], поскольку типы объектов могут содержать поля, которые определяют аргументы или содержат ссылки на интерфейсы и объединения, ни один из которых не подходит для использования в качестве входного аргумента . По этой причине входные объекты имеют в системе отдельный тип.

Это «официальная причина», но есть несколько практических причин, по которым вы не можете использовать тип объекта в качестве типа входного объекта или использовать тип объекта в качестве типа входного объекта:

Функциональность

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

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

type Student {
  name: String
  grade: Grade
}

input StudentInput {
  name: String
  grade: Grade
}

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

type Student {
  name(preferred: Boolean): String
  grade: Grade
}

input StudentInput {
  name: String
  grade: Grade = F
}

Ограничения системы типов

Типы в GraphQL сгруппированы по типам вывода и типам ввода. .

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

Эти две группы пересекаются (т.е. скаляры, перечисления, списки и ненулевые значения). Однако абстрактные типы, такие как объединения и интерфейсы, не имеют смысла во входном контексте и не могут использоваться в качестве входных данных. Разделение типов объектов и типов объектов ввода позволяет гарантировать, что абстрактный тип никогда не будет использоваться там, где ожидается тип ввода.

Схема проектирования

При представлении сущности в вашей схеме вполне вероятно, что некоторые сущности действительно будут «разделять поля» между соответствующими типами ввода и вывода:

type Student {
  firstName: String
  lastName: String
  grade: Grade
}

input StudentInput {
  firstName: String
  lastName: String
  grade: Grade
}

Однако типы объектов могут (и на самом деле часто делают) моделировать очень сложные структуры данных:

type Student {
  fullName: String!
  classes: [Class!]!
  address: Address!
  emergencyContact: Contact
  # etc
}

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

Более того, даже для относительно простых сущностей мы часто предъявляем разные требования относительно допустимости значений NULL между типами объектов и их «двойниками» входных объектов. Часто мы хотим гарантировать, что поле также будет возвращено в ответе, но мы не хотим, чтобы те же поля были обязательными для нашего ввода. Например,

type Student {
  firstName: String!
  lastName: String!
}

input StudentInput {
  firstName: String
  lastName: String
}

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

input CreateUserInput {
  firstName: String!
  lastName: String!
  email: String!
  password: String!
}

input UpdateUserInput {
  email: String
  password: String
}

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

Дэниел Риарден
источник
4
это действительно отличный ответ!
Чарли Нг
Поправьте меня, если я ошибаюсь, но в вашем примере тип Gradeнельзя повторно использовать во входных данных StudentInput, верно? Вам нужно будет либо встроить поля в объект ввода, либо иметь GradeInputобъект ввода.
Мэтт
2
@Matt Хороший вопрос! В приведенном выше примере Gradeэто тип перечисления. В отличие от объектов, скалярные типы (например , String, Int и т.д.) и перечислений типов могут быть использованы в качестве обоих типов входных и выходных типов.
Дэниел Риарден
хорошее объяснение
Висах Виджаян
Действительно отличный ответ!
Джонни
25

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

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

ОБНОВИТЬ

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

LB2
источник
4
Здесь немного подробнее обсуждается, почему существует это ограничение: github.com/graphql/graphql-js/issues/599
Кэм Джексон,