Tipos de Datos y Schemas en GraphQL – Parte 2 (Final)
En esta página:
En la parte anterior llamada Tipos de Datos y Schemas en GraphQL – Parte 1, pudimos apreciar los primeros tipos de datps y schemas (esquemas) que tiene la tecnología GraphQL. Como mencione en la primera parte, estamos viendo una descripción técnica de estos tipos de datos, en otro post veremos a como consumir datos usando GraphQL, pero primero es importante conocer estos conceptos importantes sobre los tipos de datos y schemas. En este segunda parte y última veremos otro grupo de Tipos de Datos y Schemas en GraphQL, vamos con ello.
Partes
- Parte 1
- Parte 2 (Final)
Antes de continuar te invito a escuchar el Podcast: “Si No Tienes Experiencia Para Un Puesto De Trabajo, Créala !” y “Consejos Para Entrenar Tu Memoria de Programador” (Anchor Podcast):
Spotify: | Sound Cloud: | Apple Podcasts | Anchor Podcasts |
Bien ahora continuemos con el Post: Tipos de Datos y Schemas en GraphQL – Parte 2 (Final).
Tipos Escalares
Un tipo de objeto GraphQL tiene un nombre y campos, pero en algún momento esos campos deben resolverse en algunos datos concretos. Ahí es donde entran los tipos escalares: representan las hojas de la consulta.
Por ejemplo tenemos los siguientes datos :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "data": { "postre": { "nombre": "Gelatina", "saboresDisponibles": [ "Fresa", "Naranja", "Menta" ] } } } |
De los datos anteriores, hacemos la siguiente consulta:
1 2 3 4 5 6 7 8 |
{ postre { nombre saboresDisponibles } } |
En la consulta anterior, los campos nombre y saboresDisponibles resolverán en tipos escalares, porque esos campos no tienen subcampos: son las hojas de la consulta.
GraphQL viene con un conjunto de tipos escalares predeterminados listos para usar:
Int: Un entero de 32 bits con signo.
Float: Un valor de punto flotante de precisión doble con signo.
String: Una secuencia de caracteres UTF‐8.
Boolean: true o false.
ID: Este tipo escalar representa un identificador único, que a menudo se usa para recuperar un objeto o como clave para un caché. El tipo de ID se serializa de la misma manera que una Cadena; sin embargo, definirlo como ID significa que no está destinado a ser legible por humanos.
En la mayoría de las implementaciones de servicios de GraphQL, también existe una forma de especificar tipos escalares personalizados. Por ejemplo, podríamos definir un tipo Date:
1 2 3 |
scalar Date |
Luego depende de nuestra implementación definir cómo se debe serializar, deserializar y validar ese tipo. Por ejemplo, puedes especificar que el tipo Date siempre debe serializarse en una marca de tiempo de número entero, y tu cliente debe saber esperar ese formato para cualquier campo de fecha.
Tipos de Enumeración
También llamados Enums , los tipos de enumeración son un tipo especial de escalar que está restringido a un conjunto particular de valores permitidos. Esto te permite:
- Validar que cualquier argumento de este tipo sea uno de los valores permitidos.
- Comunicar a través del sistema de tipos que un campo siempre será uno de un conjunto finito de valores.
Así es como se vería una definición de enumeración en el lenguaje de esquema GraphQL:
1 2 3 4 5 6 7 |
enum Sabor { Fresa Naranja Papaya } |
Esto significa que siempre que usemos el tipo Sabor en nuestro esquema, esperamos que sea exactamente uno de Fresa, Naranja o Papaya.
Ten en cuenta que las implementaciones del servicio GraphQL en varios lenguajes tendrán su propia forma específica de lenguaje para tratar con las enumeraciones. En lenguajes que admiten enumeraciones como un ciudadano de primera clase, la implementación podría aprovechar eso; en un lenguaje como JavaScript sin compatibilidad con enumeraciones, estos valores pueden asignarse internamente a un conjunto de enteros. Sin embargo, estos detalles no se filtran al cliente, que puede operar completamente en términos de los nombres de cadena de los valores de enumeración.
Listas y Non-Null
Los tipos de objetos, escalares y enumeraciones son los únicos tipos de tipos que puedes definir en GraphQL. Pero cuando usas los tipos en otras partes del esquema (schema), o en tus declaraciones de variables de consulta, puedes aplicar modificadores de tipo adicionales que afectan la validación de esos valores. Veamos un ejemplo:
1 2 3 4 5 6 |
type Jugo { nombre: String! saboresDisponibles: [Sabor!]! } |
Aquí, estamos usando un tipo String y marcándolo como no nulo agregando un signo de exclamación, ! después del nombre del tipo. Esto significa que nuestro servidor siempre espera devolver un valor no nulo para este campo, y si termina obteniendo un valor nulo, en realidad desencadenará un error de ejecución de GraphQL, lo que le informará al cliente que algo salió mal.
El modificador de tipo Non-Null también se puede usar al definir argumentos para un campo, lo que hará que el servidor GraphQL devuelva un error de validación si se pasa un valor nulo como ese argumento, ya sea en la cadena GraphQL o en las variables.
Obtenemos el siguiente error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "errors": [ { "message": "Variable \"$id\" of non-null type \"ID!\" must not be null.", "locations": [ { "line": 1, "column": 17 } ] } ] } |
Al realizar la siguiente consulta:
1 2 3 4 5 6 7 8 9 10 11 12 |
query HumanoById($id: ID!) { humano(id: $id) { nombre } } // Variables { "id": null } |
Las listas funcionan de manera similar: podemos usar un modificador de tipo para marcar un tipo como List, lo que indica que este campo devolverá una matriz de ese tipo. En el lenguaje de esquema, esto se denota envolviendo el tipo entre corchetes [y ]. Funciona igual para los argumentos, donde el paso de validación esperará una matriz para ese valor.
Los modificadores Non-Null y List se pueden combinar. Por ejemplo, puedes tener una lista de cadenas no nulas:
1 2 3 |
miCampo: [String!] |
Esto significa que la lista en sí puede ser nula, pero no puede tener ningún miembro nulo. Por ejemplo, en JSON:
1 2 3 4 5 6 |
miCampo: null // valido miCampo: [] // valido miCampo: ['a', 'b'] // valido miCampo: ['a', null, 'b'] // error |
Ahora, digamos que definimos una lista de cadenas no nulas:
1 2 3 |
miCampo: [String]! |
Esto significa que la lista en sí no puede ser nula, pero puede contener valores nulos:
1 2 3 4 5 6 |
miCampo: null // error miCampo: [] // valido miCampo: ['a', 'b'] // valido miCampo: ['a', null, 'b'] // valido |
Puedes anidar arbitrariamente cualquier número de modificadores No nulos y de lista, según tus necesidades.
Interfaces
Como muchos sistemas de tipos, GraphQL admite interfaces. Una interfaz es un tipo abstracto que incluye un determinado conjunto de campos que un tipo debe incluir para implementar la interfaz.
Por ejemplo, podrías tener una interfaz Character que represente a cualquier usuario de una organización:
1 2 3 4 5 6 7 8 |
interface Character { id: ID! nombre: String! pasatiempos: [Character] estudios: [Estudio]! } |
Esto significa que cualquier tipo que implemente Character necesita tener estos campos exactos, con estos argumentos y tipos de retorno.
Por ejemplo, aquí hay algunos tipos que podrían implementar Character:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
type Humano1 implements Character { id: ID! nombre: String! amigos: [Character] centrosEstudios: [Universidades]! deporteFavorito: [Deporte] totalCreditos: Int } type Humano2 implements Character { id: ID! nombre: String! amigos: [Character] centrosEstudios: [Universidades]! apellidos: String } |
Puedes ver que ambos tipos tienen todos los campos de la interfaz Character, pero también traen campos adicionales, deporteFavorito, totalCreditos y apellidos que son específicos para cada tipo de Humano en particular.
Las interfaces son útiles cuando deseas devolver un objeto o un conjunto de objetos, pero pueden ser de varios tipos diferentes.
Por ejemplo al hacer la siguiente consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
query UsuarioOrganizacion($org: Organizacion!) { usuario(organizacion: $org) { nombre apellidos } } // Variables { "org": "Alphabet" } |
Se produce un error:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "errors": [ { "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Humano2\"?", "locations": [ { "line": 4, "column": 5 } ] } ] } |
El campo usuario devuelve el tipo Character, lo que significa que puede ser a Humano1 o a Humano2 dependiendo del argumento organizacion. En la consulta anterior, solo se puede solicitar campos que existen en la interfaz Character, que no incluye primaryFunction.
Para solicitar un campo en un tipo de objeto específico, debes usar un fragmento en línea, por ejemplo hacemos la siguiente consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
query UsuarioOrganizacion($org: Organizacion!) { usuario(organizacion: $org) { nombre ... on Humano2 { apellidos } } } // Variables { "org": "Juan Castro" } |
Obtenemos los siguientes datos:
1 2 3 4 5 6 7 8 9 10 |
{ "data": { "usuario": { "nombre": "Juan", "apellidos": "Castro Lurita" } } } |
Puedes obtener más información sobre esto en la sección de fragmentos en línea en la guía de consulta de la documentación oficial de GraphQL.
Tipos de Unión
Los tipos de unión son muy similares a las interfaces, pero no pueden especificar ningún campo común entre los tipos.
1 2 3 |
union ResultadosBusqueda = Humano1 | Humano2 | Organizacion |
Siempre que devolvamos un tipo ResultadosBusqueda en nuestro esquema, podemos obtener un Humano1, un Humano2 y una Planta. Ten en cuenta que los miembros de un tipo de unión deben ser tipos de objetos concretos; no puedes crear un tipo de unión a partir de interfaces u otras uniones.
En este caso, si consultas un campo que devuelve el tipo de unión ResultadosBusqueda, debes usar un fragmento en línea para poder consultar cualquier campo.
Hacemos la siguiente consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{ search(text: "an") { __typename ... on Humano1 { nombre estatura } ... on Humano2 { nombre apellidos } ... on Organizacion { nombre area } } } |
Y obtenemos los siguientes datos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "data": { "search": [ { "__typename": "Humano1", "nombre": "Carlos", "estatura": 1.75 }, { "__typename": "Humano2", "nombre": "Luis", "apellidos": "Rojas Torres" }, { "__typename": "Organizacion", "nombre": "Alphabet", "area": "Tecnología" } ] } } |
El campo __typename se resuelve en un String que le permite diferenciar diferentes tipos de datos entre sí en el cliente.
Además, en este caso, dado que Humano1 y Humano2 comparten una interfaz común ( Character), puedes consultar los campos comunes en un solo lugar en lugar de tener que repetir los mismos campos en varios tipos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ search(text: "an") { __typename ... on Character { nombre } ... on Humano1 { estatura } ... on Humano2 { apellidos } ... on Organizacion { nombre area } } } |
Ten en cuenta que nombre todavía se especifica en Organizacion, porque de lo contrario, no aparecería en los resultados dado que Organizacion no es un Character!
Tipos de Entrada
Hasta ahora, solo hemos hablado de pasar valores escalares, como enumeraciones o cadenas, como argumentos en un campo. Pero también puedes pasar fácilmente objetos complejos. Esto es particularmente valioso en el caso de las mutaciones, donde es posible que desees pasar un objeto completo para crearlo. En el lenguaje de esquema de GraphQL, los tipos de entrada tienen exactamente el mismo aspecto que los tipos de objetos normales, pero con la palabra clave input en lugar de type:
1 2 3 4 5 6 |
input Calificacion { stars: Int! comentario: String } |
Para usar el tipo de objeto de entrada (input) en una mutación, podemos hacer la siguiente consulta:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
mutation CrearCalificacionPelicula($pel: Pelicula!, $calificacion: EntradaCalificacion!) { crearCalificacion(pelicula: $pel, calificacion: $calificacion) { stars comentario } } // Variables { "pel": "Titanic", "calificacion": { "stars": 8, "comentario": "Esta es una gran película!" } } |
Obtenemos el siguiente resultado:
1 2 3 4 5 6 7 8 9 10 |
{ "data": { "crearCalificacion": { "stars": 8, "comentario": "Esta es una gran película!" } } } |
Los campos en un tipo de objeto de entrada (input) pueden hacer referencia a tipos de objetos de entrada, pero no puede mezclar tipos de entrada y salida en su esquema. Los tipos de objetos de entrada tampoco pueden tener argumentos en sus campos. Como menciona al inicio de este Post, en otro tutorial, ya sea en el blog o en nuestro canal de YouTube, te enseñaremos a como usar estos tipos de datos, consumiendo los datos de una API REST por ejemplo.
Bien, hasta aqui terminamos con todos los tipos de datos y esquemas que tiene GraphQL.
Conclusión
Conocer los tipos de datos que podemos utilizar en GraphQL, nos servirá de guía para poder consumir y leer datos de por ejemplo una API REST. Al conocer estos tipos de datos, evitaremos los errores en lo posible, que suceden cuando queremos llamar a nuestros datos del servidor.
Nota (s)
- No olvides que debemos usar la Tecnología para hacer cosas Buenas por el Mundo.
Síguenos en nuestras Redes Sociales para que no te pierdas nuestros próximos contenidos.
- GraphQL
- 24-03-2023
- 13-04-2023
- Crear un Post - Eventos Devs - Foro
Social
Redes Sociales (Developers)
Redes Sociales (Digital)