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

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

Например:

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 + свойствами в схеме, которые станут обузой.

Ответ 1

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

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

ОБНОВИТЬ

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

Ответ 2

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

Тип объекта 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
}

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

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

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
}

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