¿Cuál es el punto del tipo de entrada en GraphQL?

83

¿Podría explicar por qué si el argumento de entrada de la mutación es un objeto, debería ser un tipo de entrada ? Creo que es mucho más simple reutilizar el tipo sin proporcionar una identificación.

Por ejemplo:

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
}

Está bien para objetos pequeños, pero cuando tienes muchos objetos con más de 10 propiedades en el esquema, eso se convierte en una carga.

Dmitry Dushkin
fuente
30
Los objetos de entrada deben ser serializables. Dado que los objetos de salida pueden contener ciclos, no se pueden reutilizar para la entrada.
Jesse Buchanan
1
Jesse, parece suficiente respuesta! Puedes contestar y lo marco así.
Dmitry Dushkin
Preguntándose si es posible combinar interfaces con él
MVCDS

Respuestas:

99

De la especificación:

El tipo de objeto GraphQL (ObjectTypeDefinition) ... no es apropiado para su reutilización [como entrada], porque los tipos de objeto pueden contener campos que definen argumentos o contener referencias a interfaces y uniones, ninguno de los cuales es apropiado para su uso como argumento de entrada . Por esta razón, los objetos de entrada tienen un tipo separado en el sistema.

Esa es la "razón oficial", pero hay varias razones prácticas por las que no puede usar un tipo de objeto como tipo de objeto de entrada o usar un tipo de objeto como tipo de objeto de entrada:

Funcionalidad

Los tipos de objetos y los tipos de objetos de entrada tienen campos; sin embargo, esos campos tienen propiedades diferentes que reflejan cómo el esquema usa estos tipos. Su esquema definirá potencialmente argumentos y algún tipo de función de resolución para los campos de un tipo de objeto, pero estas propiedades no tienen sentido en un contexto de entrada (es decir, no puede resolver el campo de un objeto de entrada, ya tiene un valor explícito) . Del mismo modo, los valores predeterminados solo se pueden proporcionar para campos de tipo de objeto de entrada y no para campos de tipo de objeto.

En otras palabras, esto puede parecer una duplicación:

type Student {
  name: String
  grade: Grade
}

input StudentInput {
  name: String
  grade: Grade
}

Pero agregar características específicas para tipos de objetos o tipos de objetos de entrada deja en claro que se comportan de manera diferente:

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

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

Limitaciones del sistema de tipos

Los tipos en GraphQL se agrupan en tipos de salida y tipos de entrada .

Los tipos de salida son tipos que pueden devolverse como parte de una respuesta producida por un servicio GraphQL. Los tipos de entrada son tipos que son entradas válidas para argumentos de campo o de directiva.

Hay superposición entre estos dos grupos (es decir, escalares, enumeraciones, listas y no nulos). Sin embargo, los tipos abstractos como uniones e interfaces no tienen sentido en un contexto de entrada y no se pueden usar como entradas. Separar los tipos de objetos y los tipos de objetos de entrada le permite asegurarse de que nunca se utilice un tipo abstracto donde se espera un tipo de entrada.

Diseño de esquemas

Al representar una entidad en su esquema, es probable que algunas entidades "compartan campos" entre sus respectivos tipos de entrada y salida:

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

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

Sin embargo, los tipos de objetos pueden (y en realidad lo hacen con frecuencia) modelar estructuras de datos muy complejas:

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

Si bien estas estructuras pueden traducirse en entradas apropiadas (creamos un Estudiante, por lo que también pasamos un objeto que representa su dirección), a menudo no lo hacen, es decir, tal vez necesitemos especificar las clases del estudiante por ID de clase e ID de sección, no un objeto. De manera similar, es posible que tengamos campos que queramos devolver, pero que no queremos mutar, o viceversa (como un passwordcampo).

Además, incluso para entidades relativamente simples, a menudo tenemos diferentes requisitos en torno a la nulabilidad entre los tipos de objetos y sus objetos de entrada "contrapartes". A menudo queremos garantizar que un campo también se devuelva en una respuesta, pero no queremos que los mismos campos sean obligatorios en nuestra entrada. Por ejemplo,

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

input StudentInput {
  firstName: String
  lastName: String
}

Por último, en muchos esquemas, a menudo no existe un mapeo uno a uno entre el tipo de objeto y el tipo de objeto de entrada para una entidad determinada. Un patrón común es utilizar tipos de objetos de entrada separados para diferentes operaciones para ajustar aún más la validación de entrada a nivel de esquema:

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

input UpdateUserInput {
  email: String
  password: String
}

Todos estos ejemplos ilustran un punto importante: si bien un tipo de objeto de entrada puede reflejar un tipo de objeto en algunas ocasiones, es mucho menos probable que lo vea en los esquemas de producción debido a los requisitos comerciales.

Daniel Rearden
fuente
4
esta es de hecho una gran respuesta!
Charlie Ng
Corrígeme si me equivoco, pero en tu ejemplo, el tipo Gradeno se puede reutilizar en la entrada StudentInput, ¿verdad? Deberá incluir campos en línea en el objeto de entrada o tener un GradeInputobjeto de entrada.
Matt
2
@ Matt ¡Buena pregunta! En el ejemplo anterior, Gradees un tipo de enumeración. A diferencia con los objetos, tipos escalares (como String, Int, etc.) y tipos enumeraciones se pueden utilizar como ambos tipos de entrada y tipos de salida.
Daniel Rearden
bonita explicación
Visakh Vijayan
¡Gran respuesta de hecho!
Johnny
25

El comentario de Jesse es correcto. Para obtener una respuesta más formal, aquí está el extracto de la documentación de GraphQL sobre los tipos de entrada :

El tipo de Objeto definido anteriormente no es apropiado para su reutilización aquí, porque los Objetos pueden contener campos que expresan referencias circulares o referencias a interfaces y uniones, ninguno de los cuales es apropiado para su uso como argumento de entrada. Por esta razón, los objetos de entrada tienen un tipo separado en el sistema.

ACTUALIZAR

Desde que lo publiqué, descubrí que las referencias circulares en realidad son aceptables, siempre que sean nilables (o de lo contrario declararía una cadena infinita). Sin embargo, aún existen otras limitaciones (por ejemplo, interfaces) que parecen necesitar un sistema de tipos separado para las entradas.

LB2
fuente
4
Aquí hay un poco más de discusión sobre por qué existe esta restricción: github.com/graphql/graphql-js/issues/599
Cam Jackson