Detalles de GraphQL: qué, cómo y por qué

GraphQL es ahora, sin exagerar, este es el último pío del modo IT. Y si aún no sabe qué tipo de tecnología es, cómo usarla y por qué puede serle útil, entonces el artículo que publicamos hoy está escrito para usted. Aquí repasaremos los conceptos básicos de GraphQL usando un ejemplo de implementación de un esquema de datos para la API de una empresa de palomitas de maíz. En particular, hablemos sobre los tipos de datos, consultas y mutaciones.



¿Qué es GraphQL?


GraphQL es un lenguaje de consulta utilizado por las aplicaciones cliente para trabajar con datos. GraphQL está asociado con un concepto como un "esquema"; esto es lo que le permite organizar la creación, lectura, actualización y eliminación de datos en su aplicación (es decir, tenemos cuatro funciones básicas que se utilizan al trabajar con almacenes de datos, a las que generalmente se hace referencia con el acrónimo CRUD) - Crear, leer, actualizar, eliminar).

Se dijo anteriormente que GraphQL se utiliza para trabajar con datos en "su aplicación" y no "en su base de datos". El hecho es que GraphQL es un sistema independiente de las fuentes de datos, es decir, no importa dónde esté organizado para organizar su trabajo.

Si observa, sin saber nada sobre GraphQL, sobre el nombre de esta tecnología, puede parecer que nos enfrentamos a algo muy complicado y confuso. El nombre de la tecnología tiene la palabra "Gráfico". ¿Significa esto que para dominarlo, debe aprender a trabajar con bases de datos de gráficos? Y el hecho de que el nombre contenga "QL" (que puede significar "lenguaje de consulta", es decir, "lenguaje de consulta"), ¿significa que aquellos que quieran usar GraphQL tendrán que aprender un lenguaje de programación completamente nuevo?

Estos temores no están completamente justificados. Para tranquilizarlo, esta es la cruel verdad acerca de esta tecnología: simplemente embellece las solicitudes GET o POST . Si bien GraphQL, en general, presenta algunos conceptos nuevos relacionados con la organización de datos y la interacción con ellos, los mecanismos internos de esta tecnología se basan en las buenas solicitudes HTTP.

Repensar la tecnología REST


La flexibilidad es lo que diferencia a la tecnología GraphQL de la conocida tecnología REST. Cuando se utiliza REST, si todo se hace correctamente, los puntos finales generalmente se crean teniendo en cuenta las características de un determinado recurso o tipo de datos de aplicación.

Por ejemplo, cuando se realiza una solicitud GET al punto final /api/v1/flavors se espera que envíe una respuesta similar a esta:

 [ {  "id": 1,   "name": "The Lazy Person's Movie Theater",   "description": "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }, {   "id": 2,   "name": "What's Wrong With You Caramel",   "description": "You're a crazy person that likes sweet popcorn. Congratulations." }, {   "id": 3,   "name": "Gnarly Chili Lime",   "description": "The kind of popcorn you make when you need a good smack in the face."} ] 

No hay nada catastróficamente incorrecto con esta respuesta, pero pensemos en la interfaz de usuario, o más bien, en cómo pretendemos consumir estos datos.

Si queremos mostrar una lista simple en la interfaz que contiene solo los nombres de los tipos de palomitas de maíz disponibles (y nada más), entonces esta lista puede parecerse a la que se muestra a continuación.


Lista de tipos de palomitas de maíz

Se puede ver que aquí estamos en una situación difícil. Podemos decidir no utilizar el campo de description , pero ¿vamos a sentarnos y fingir que no enviamos este campo al cliente? ¿Qué más podemos hacer? Y cuando, después de unos meses, nos pregunten por qué la aplicación es tan lenta para los usuarios, solo tenemos que dejar al tipo y ya no nos reuniremos con la administración de la empresa para la que hicimos esta aplicación.

De hecho, el hecho de que el servidor envíe datos innecesarios en respuesta a una solicitud del cliente no es completamente culpa nuestra. REST es un mecanismo de adquisición de datos que se puede comparar con un restaurante en el que el camarero le pregunta al visitante: "¿Qué quieres?", Y, sin prestar especial atención a sus deseos, le dice: "Te traeré lo que tenemos" .

Si descartamos los chistes, en aplicaciones reales esto puede conducir a situaciones problemáticas. Por ejemplo, podemos mostrar diversa información adicional sobre cada tipo de palomitas de maíz, como información sobre precios, información sobre el fabricante o información nutricional ("¡Palomitas de maíz veganas!"). Al mismo tiempo, los puntos finales REST inflexibles hacen que sea muy difícil obtener datos específicos sobre tipos específicos de palomitas de maíz, lo que lleva a una carga irrazonablemente alta en los sistemas y al hecho de que las soluciones resultantes están lejos de aquellas de las que los desarrolladores podrían estar orgullosos.

Cómo la tecnología GraphQL mejora para qué se utilizó la tecnología REST


Un análisis superficial de la situación descrita anteriormente puede parecer que solo somos un problema menor. "¿Qué tiene de malo enviar al cliente datos innecesarios?" Para comprender en qué medida los "datos innecesarios" pueden ser un gran problema, recuerde que GraphQL fue desarrollado por Facebook. Esta empresa tiene que atender millones de solicitudes por segundo.

¿Qué significa esto? Y el hecho de que con tales volúmenes cada pequeña cosa importa.

GraphQL, si continuamos la analogía con un restaurante, en lugar de "llevar" al visitante "lo que es", trae exactamente lo que el visitante ordena.

Podemos obtener una respuesta de GraphQL que se centra en el contexto en el que se utilizan los datos. En este caso, no necesitamos agregar puntos de acceso "únicos" al sistema, realizar muchas solicitudes o escribir estructuras condicionales de varios pisos.

¿Cómo funciona GraphQL?


Como ya dijimos, GraphQL se basa en simples solicitudes GET o POST para transmitir datos al cliente y recibirlos de él. Si consideramos esta idea con más detalle, resulta que hay dos tipos de consultas en GraphQL. El primer tipo incluye solicitudes de lectura de datos, que en la terminología de GraphQL simplemente se llaman consultas y se refieren a la letra R (lectura, lectura) del acrónimo CRUD. Las consultas del segundo tipo son solicitudes de modificación de datos, que se llaman mutaciones en GraphQL. Se relacionan con los cuadros de eje C, U y D del acrónimo CRUD, es decir, los usan para crear, crear, actualizar y eliminar registros.

Todas estas solicitudes y mutaciones se envían a la URL del servidor GraphQL, que, por ejemplo, puede verse como https://myapp.com/graphql , en forma de solicitudes GET o POST . Hablaremos más sobre esto a continuación.

Consultas GraphQL


Las consultas GraphQL son entidades que representan una solicitud al servidor para recibir ciertos datos. Por ejemplo, tenemos una determinada interfaz de usuario que queremos llenar con datos. Para estos datos, recurrimos al servidor, ejecutando la solicitud. Cuando se usan las API REST tradicionales, nuestra solicitud toma la forma de una solicitud GET. Cuando se trabaja con GraphQL, se utiliza una nueva sintaxis de consulta:

 { flavors {   name } } 

¿Eso es JSON? O un objeto de JavaScript? Ni lo uno ni lo otro. Como ya hemos dicho, en nombre de la tecnología GraphQL, las dos últimas letras, QL, significan "lenguaje de consulta", es decir, el lenguaje de consulta. Este es, literalmente, un nuevo lenguaje para escribir solicitudes de datos. Todo esto suena como una descripción de algo bastante complicado, pero de hecho no hay nada complicado aquí. Analicemos la consulta anterior:

 { //    ,   . } 

Todas las solicitudes comienzan con una "solicitud raíz", y lo que necesita obtener durante la ejecución de la solicitud se denomina campo. Para evitar confusiones, es mejor llamar a estas entidades "campos de consulta en el esquema". Si ese nombre no le parece claro, espere un poco, a continuación hablaremos más sobre el esquema. Aquí, en la consulta raíz, solicitamos el campo de flavors .

 { flavors {   //  ,        flavor. } } 

Al solicitar un determinado campo, también debemos indicar los campos anidados que deben recibirse para cada objeto que viene en respuesta a la solicitud (incluso si se espera que solo un objeto venga en respuesta a la solicitud).

 { flavors {   name } } 

¿Cuál será el resultado? Después de enviar una solicitud de este tipo al servidor GraphQL, obtendremos una respuesta ordenada bien formada como la siguiente:

 { "data": {   "flavors": [     { "name": "The Lazy Person's Movie Theater" },     { "name": "What's Wrong With You Caramel" },     { "name": "Gnarly Chili Lime" }   ] } } 

Tenga en cuenta que no hay nada superfluo. Para hacerlo más claro, aquí hay otra solicitud que se ejecuta para obtener datos en otra página de la aplicación:

 { flavors {   id   name   description } } 

En respuesta a esta solicitud, obtenemos lo siguiente:

 { "data": {   "flavors": [     { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" },     { "id": 2, "name": "What's Wrong With You Caramel", description: "You're a crazy person that likes sweet popcorn. Congratulations." },     { "id": 3, "name": "Gnarly Chili Lime", description: "A friend told me this would taste good. It didn't. It burned my kernels. I haven't had the heart to tell him." }   ] } } 

Como puede ver, GraphQL es una tecnología muy poderosa. Pasamos al mismo punto final, y las respuestas a las solicitudes corresponden exactamente a lo que se necesita para llenar la página desde la que se ejecutan estas solicitudes.

Si necesitamos obtener solo un objeto de flavor , entonces podemos aprovechar el hecho de que GraphQL puede trabajar con argumentos:

 { flavors(id: "1") {   id   name   description } } 

Aquí establecemos rígidamente el identificador específico ( id ) del objeto en el código, la información sobre la que necesitamos, pero en tales casos podemos usar identificadores dinámicos:

 query getFlavor($id: ID) { flavors(id: $id) {   id   name   description } } 

Aquí, en la primera línea, le damos un nombre a la solicitud (el nombre se elige arbitrariamente, getFlavor se puede reemplazar con algo como pizza , y la solicitud seguirá siendo operativa) y declaramos las variables que la solicitud espera. En este caso, se supone que el identificador ( id ) de la ID tipo escalar se pasará a la solicitud (hablaremos de los tipos a continuación).

Independientemente de si id utiliza una id estática o dinámica al ejecutar una solicitud, así es como se verá la respuesta a una solicitud similar:

 { "data": {   "flavors": [     { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }   ] } } 

Como puede ver, todo está organizado de manera muy conveniente. Probablemente esté empezando a pensar en usar GraphQL en su propio proyecto. Y, aunque de lo que ya hemos hablado se ve maravilloso, la belleza de GraphQL realmente se manifiesta donde funciona con campos anidados. Supongamos que en nuestro esquema hay otro campo llamado nutrition que contiene información sobre el valor nutricional de los diferentes tipos de palomitas de maíz:

 { flavors {   id   name   nutrition {     calories     fat     sodium   } } } 

Puede parecer que en nuestro almacén de datos, cada objeto de flavor contendrá un objeto de nutrition anidado. Pero esto no es del todo cierto. Con GraphQL, puede combinar llamadas a fuentes de datos independientes pero relacionadas en una sola consulta, lo que le permite recibir respuestas que brindan la conveniencia de trabajar con datos incrustados sin la necesidad de desnormalizar la base de datos:

 { "data": {   "flavors": [     {       "id": 1,       "name": "The Lazy Person's Movie Theater",       "nutrition": {         "calories": 500,         "fat": 12,         "sodium": 1000       }     },     ...   ] } } 

Esto puede aumentar significativamente la productividad del programador y la velocidad del sistema.

Hasta ahora, hemos hablado de solicitudes de lectura. ¿Qué pasa con las solicitudes de actualización de datos? ¿Usarlos nos da la misma conveniencia?

Mutaciones GraphQL


Si bien las consultas GraphQL cargan datos, las mutaciones son responsables de realizar cambios en los datos. Las mutaciones se pueden usar en forma del mecanismo básico RPC (Llamada a procedimiento remoto) para resolver diversas tareas, como enviar datos de usuario a una API de terceros.

Al describir mutaciones, se usa una sintaxis que se asemeja a la que usamos al generar consultas:

 mutation updateFlavor($id: ID!, $name: String, $description: String) { updateFlavor(id: $id, name: $name, description: $description) {   id   name   description } } 

Aquí declaramos la mutación updateFlavor , especificando algunas variables: id , name y description . Actuando de acuerdo con el mismo esquema que se utiliza para describir las consultas, "dibujamos" campos variables (mutación raíz) usando la palabra clave de mutation , seguido de un nombre que describe la mutación y un conjunto de variables que son necesarias para formar la solicitud de cambio de datos correspondiente.

Estas variables incluyen lo que estamos tratando de cambiar o qué mutación queremos causar. Tenga en cuenta también que después de la mutación, podemos solicitar la devolución de algunos campos.

En este caso, necesitamos obtener, después de cambiar el registro, los campos id , name y description . Esto puede ser útil al desarrollar algo como interfaces optimistas, eliminando la necesidad de cumplir una solicitud para recibir datos modificados después de cambiarlos.

Diseñando un esquema y conectándolo a un servidor GraphQL


Hasta ahora, hemos hablado sobre cómo funciona GraphQL en el cliente y cómo ejecutan las consultas. Ahora hablemos sobre cómo responder a estas solicitudes.

▍ Servidor gráfico


Para ejecutar una consulta GraphQL, necesita un servidor GraphQL al que pueda enviar dicha consulta. Un servidor GraphQL es un servidor HTTP normal (si escribe en JavaScript, puede ser un servidor creado con Express o Hapi), al que se adjunta un diagrama GraphQL.

 import express from 'express' import graphqlHTTP from 'express-graphql' import schema from './schema' const app = express() app.use('/graphql', graphqlHTTP({ schema: schema, graphiql: true })) app.listen(4000) 

Al "unir" un esquema, nos referimos a un mecanismo que pasa las solicitudes recibidas del cliente a través del esquema y devuelve las respuestas. Es como un filtro de aire a través del cual el aire ingresa a la habitación.

El proceso de "filtrado" está asociado con solicitudes o mutaciones enviadas por el cliente al servidor. Tanto las consultas como las mutaciones se resuelven utilizando funciones relacionadas con los campos definidos en la consulta raíz o en la mutación raíz del esquema.

Lo anterior es un marco de ejemplo de servidor HTTP creado utilizando la biblioteca Express JavaScript. Usando la función express-graphql de Facebook, "adjuntamos" el esquema (se supone que se describe en un archivo separado) y ejecutamos el servidor en el puerto 4000. Es decir, los clientes, hablando sobre el uso local de este servidor, podrán enviar solicitudes dirección http://localhost:4000/graphql .

▍ Tipos de datos y resolvers


Para garantizar el funcionamiento del servidor GraphQL, debe preparar el esquema y adjuntarlo.

Recuerde que hablamos sobre declarar campos en una consulta raíz o en una mutación raíz anterior.

 import gql from 'graphql-tag' import mongodb from '/path/to/mongodb' //  -  . ,  `mongodb`     MongoDB. const schema = { typeDefs: gql`   type Nutrition {     flavorId: ID     calories: Int     fat: Int     sodium: Int   }   type Flavor {     id: ID     name: String     description: String     nutrition: Nutrition   }   type Query {     flavors(id: ID): [Flavor]   }   type Mutation {     updateFlavor(id: ID!, name: String, description: String): Flavor   } `, resolvers: {   Query: {     flavors: (parent, args) => {       // ,  args  ,  { id: '1' }       return mongodb.collection('flavors').find(args).toArray()     },   },   Mutation: {     updateFlavor: (parent, args) => {       // ,  args    { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' }       //  .       mongodb.collection('flavors').update(args)       //  flavor  .       return mongodb.collection('flavors').findOne(args.id)     },   },   Flavor: {     nutrition: (parent) => {       return mongodb.collection('nutrition').findOne({         flavorId: parent.id,       })     }   }, }, } export default schema 

La definición de campos en un esquema GraphQL consta de dos partes: desde declaraciones de tipo ( typeDefs ) y resolver . La typeDefs contiene declaraciones de tipo para los datos utilizados en la aplicación. Por ejemplo, anteriormente hablamos sobre una solicitud para obtener una lista de objetos de flavor del servidor. Para realizar una solicitud similar a nuestro servidor, debe realizar los siguientes tres pasos:

  1. Indique al esquema cómo se ven los datos del objeto de flavor (en el ejemplo anterior, se ve como un anuncio de type Flavor ).
  2. Declare el campo en el campo raíz de type Query (esta es la propiedad de flavors del type Query valor).
  3. Declare una función de reconocimiento de objetos resolvers.Query escrita de acuerdo con los campos declarados en el campo raíz de type Query .

Ahora prestemos atención a typeDefs . Aquí damos la información del esquema sobre la forma de nuestros datos. En otras palabras, le contamos a GraphQL sobre las diversas propiedades que pueden estar contenidas en entidades del tipo correspondiente.

 type Flavor { id: ID name: String description: String nutrition: Nutrition } 

Una declaración de type Flavor indica que un objeto de flavor puede contener un campo de ID de tipo ID , un campo de name de tipo String , un campo de description de tipo String y un campo de nutrition de tipo Nutrition .

En el caso de la nutrition usamos aquí el nombre de un tipo diferente declarado en typeDefs . Aquí, el type Nutrition construcción type Nutrition describe la forma de datos nutricionales de las palomitas de maíz.

Preste atención al hecho de que aquí, como al comienzo de este material, estamos hablando de una "aplicación" y no de una "base de datos". En el ejemplo anterior, se supone que tenemos una base de datos, pero los datos en la aplicación pueden provenir de cualquier fuente. Incluso podría ser una API de terceros o un archivo estático.

Tal como lo hicimos en la declaración de type Flavor , aquí especificamos los nombres de los campos que estarán contenidos en nutrition objetos de nutrition , usando, como tipos de datos de estos campos (propiedades), lo que en GraphQL se llama tipos de datos escalares. Al momento de escribir esto, GraphQL soportaba 5 tipos de datos escalares incorporados :

  • Int : entero de 32 bits con signo.
  • Float : un número de coma flotante de doble precisión con un signo.
  • String : una secuencia de caracteres codificados en UTF-8.
  • Boolean : Booleano true o false .
  • ID : un identificador único que a menudo se usa para cargar objetos repetidamente o como una clave en la memoria caché. Los valores de ID de tipo ID serializan de la misma manera que las cadenas, sin embargo, una indicación de que un tipo de ID tiene un valor se enfatiza por el hecho de que este valor no está destinado a mostrarse a las personas, sino para su uso en programas.

Además de estos tipos escalares, también podemos asignar propiedades a los tipos que definimos nosotros mismos. Esto es exactamente lo que hicimos asignando la propiedad nutrition descrita en el type Flavor , tipo Construcción de Nutrition .

 type Query { flavors(id: ID): [Flavor] } 

En el type Query construcción type Query , que describe el tipo raíz de Query (la "consulta raíz" de la que hablamos anteriormente), declaramos el nombre del campo que se puede solicitar. Al declarar este campo, además, junto con el tipo de datos que esperamos devolver, especificamos los argumentos que pueden venir en la solicitud.

En este ejemplo, esperamos una posible recepción del argumento id de una ID tipo escalar. En respuesta a dicha solicitud, se espera una matriz de objetos cuyo dispositivo se asemeja a un dispositivo del tipo Flavor .

▍Conectar el reconocedor de consultas


, type Query field , , -.

— , GraphQL, , «». resolvers , Query , , flavors , . flavors , type Query .

 typeDefs: gql`…`, resolvers: { Query: {   flavors: (parent, args) => {     // ,  args    { id: '1' }     return mongodb.collection('flavors').find(args).toArray()   }, }, … }, 

- . parent — , , args , . context , . «» ( — , ).

, , . GraphQL « » . , , .

GraphQL , , . JSON-, JSON-, ( GraphQL ).

- flavors MongoDB, args ( ) .find() , , .

▍


-, GraphQL, , , , nutrition . , , Nutrition , , , , flavor . , / .

GraphQL , type Flavor nutrition type Nutrition , . , , flavor .

 typeDefs: gql`   type Nutrition {     flavorId: ID     calories: Int     fat: Int     sodium: Int   }   type Flavor {     […]     nutrition: Nutrition   }   type Query {…}   type Mutation {…} `, resolvers: {   Query: {     flavors: (parent, args) => {…},   },   Mutation: {…},   Flavor: {     nutrition: (parent) => {       return mongodb.collection('nutrition').findOne({         flavorId: parent.id,       })     }   }, }, 

resolvers , , Query , Mutation Flavor . , typeDefs .

Flavors , , nutrition -. , Flavor . , : « , nutrition , type Flavor ».

MongoDB, , parent , -. , parent , , , flavors . , flavor , :

 { flavors {   id   name   nutrition {     calories   } } } 

flavor , flavors , nutrition , parent . , , , MongoDB, parent.id , id flavor , .

parent.id , nutrition flavorId , flavor .

▍


, , . , . type Mutation , , updateFlavor , , .

 type Mutation { updateFlavor(id: ID!, name: String, description: String): Flavor } 

: « , updateFlavor id ID ( , ! , GraphQL , ), name String description String ». , , Flavor ( — , id , name , description , , , nutrition ).

 { typeDefs: gql`…`, resolvers: {   Mutation: {     updateFlavor: (parent, args) => {       // ,  args    { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' }       //  .       mongodb.collection('flavors').update(         { id: args.id },         {           $set: {             ...args,           },         },       )       //  flavor  .       return mongodb.collection('flavors').findOne(args.id)     },   }, }, } 

- updateFlavor , : , , — , flavor .

, , flavor . ?

, , . , flavor , .

args ? , . , , , 100% , . , , , , , .

GraphQL?


, , , , , GraphQL-API.

, GraphQL , . , . , . , , , GraphQL REST . , , , GraphQL.

▍ ,


, HTTP-, , , , — . GraphQL , , , , ( ).

, , ( — ), GraphQL .

▍ , ,


, , « ». , , , . . GraphQL .

▍ ,


REST API, : , . , -, iOS Android, API . , , , , « » .

, , , HTTP, API (, , ).

▍ GraphQL — ? REST API GraphQL?


, . . , , GraphQL . GraphQL, . , , , . , , .

, GraphQL , , , . GraphQL , Apollo Relay, .

GraphQL — , , . graphql ( express-graphql , ) — . , GraphQL - . , -, , , , .

Resumen


, GraphQL , . GraphQL , , , . , , , , GraphQL.

, : GraphQL . GraphQL . , GraphQL, , , , , , , .

— , GraphQL — , , . GraphQL , . , GraphQL — , , , . . , , , , , , GraphQL.

Estimados lectores! GraphQL — , .

Source: https://habr.com/ru/post/445268/


All Articles