Desde su publicación en 2015 GraphQL se ha ido estableciendo en el panorama tecnológico como una interesante alternativa a REST en el diseño y la implementación de nuestras APIs.
En este artículo vamos a explicar qué es GraphQL y cuáles son sus características principales. En próximas entregas construiremos un servidor GraphQL, con operaciones CRUD sobre una serie de entidades, usando Spring Boot, graphql-java y GraphiQL.
¿Qué es GraphQL?
GraphQL es un lenguaje de consulta de APIs creado por Facebook en 2012 y liberado en 2015.
Es una alternativa a REST que usa un sistema fuerte de tipos para describir las funcionalidades de una API.
Consultas y recorrido de grafos
REST, como sabemos, es un estilo de arquitectura basado en la existencia de recursos o entidades que pueden ser accedidos públicamente. En REST todos los recursos comparten una interfaz de operaciones que permiten acceder y modificar el estado público del recurso: CREATE, DELETE, READ y UPDATE. Estas operaciones se implementan recurriendo a los verbos HTTP: POST, DELETE, GET y PUT.
Supongamos, para concretar ideas, el siguiente modelo:
Es decir, un Héroe puede ser rey de varias Ciudades, cada Ciudad tiene un Héroe fundador, y cada Ciudad venera a una Divinidad. En cierto momento, se da una situación como ésta:
Si quisiéramos seguir los principios REST, tendríamos consultas de este estilo:
Para obtener información del héroe con id=10: GET /rest/heroe/10
Para obtener información de las ciudades sobre las que reina el héroe con id=10: GET rest/heroe/10/posesiones
Para obtener información, por ejemplo, de las divinidades que veneran en las ciudades cuyo rey es un héroe dado, la cosa empieza a complicarse: GET rest/heroe/10/posesiones/divinidades
Con consultas como esta última nos alejamos del principio REST de publicar recursos manipulables mediante operaciones HTTP. En la operación anterior parece que el recurso consultado es un héroe concreto, pero en realidad lo que nos interesa son otros recursos (divinidades) relacionadas de cierta manera con él (“son veneradas en las ciudades de las que es rey”). En estos casos la solución habitual, en vez de implementar un punto de acceso semejante, es realizar varias consultas, primero una para obtener la información de héroe: GET rest/heroe/10
, y luego, para cada una de las ciudades con id=X que posea, otra consulta concreta: GET rest/ciudad/X/
.
Pensemos ahora en una consulta de las divinidades que veneran en las ciudades en las que reinan fundadores de ciudades en la que reina un héroe dado. Esto, que mediante REST parece inviable (intentemos imaginar algo como GET rest/heroe/10/ciudades/fundadores/posesiones/divinidades
sin sentir sudores fríos…), es perfectamente posible mediante GraphQL.
En efecto, GraphQL va a considerar los recursos como nodos de un determinado grafo, determinado por el estado de cada objeto. Así, supongamos que queremos el héroe con id=10:
Ahora buscamos las ciudades de ese rey (el resultado en azul):
Y si lo que queremos son las divinidades que veneran en las ciudades cuyo rey es el héroe con id=10, tendremos:
Por último, nos atreveremos con las divinidades que veneran en las ciudades en las que reinan fundadores de ciudades en la que reina un héroe dado:
En GraphQL podemos interpretar cada consulta como un trayecto o recorrido concreto a lo largo de un grafo de objetos. Lo cual nos permite una flexibilidad de la que REST no es capaz.
Esquema, tipos y consultas en GraphQL
GraphQL describe los recursos mediante un esquema (escrito en un lenguaje propio llamado SDL) que define:
Tipos de datos y relaciones entre ellos
Una serie de operaciones, de las que destacaremos las Consultas (Queries) para la lectura de datos, y los Modificadores (Mutations) para crear, modificar y borrar datos
Solucionadores (Resolvers) y manejadores de errores
Tipos
Los tipos definen las entidades que vamos a consultar o modificar, sus atributos y las relaciones con otros tipos. En SDL se emplea para ello la palabra type:
//esquema
type Heroe {
id: ID!
nombre: String!
apellido: String!
posesiones: [Ciudad]
}
type Ciudad {
id: ID!
nombre: String!
fundador: Heroe
rey: Heroe
divinidad: Divinidad
}
type Divinidad {
id: ID!
nombre: String!
epiteto: String!
}
Los atributos pueden ser de tipo escalar (Int, Float, String, Boolean and ID), o referenciar otros tipos definidos en el esquema. Las listas (relaciones 1:N) se indican con ([]). Los atributos marcados con ! no pueden ser nulos.
Operaciones
Las operaciones (Consultas y Modificadores) también son tipos, cuyos atributos declaran las operaciones disponibles. Definamos una consulta sobre la entidad Héroe:
//esquema
type Query {
heroe(nombre: String): Heroe
}
Esta consulta obtiene la información de un héroe concreto. Al realizar la consulta debemos especificar los atributos de de retorno. Por ejemplo:
//consulta GraphQL
{
heroe (nombre: "Agamenón") {
apellido
}
}
nos devuelve el apellido del héroe Agamenón.
//respuesta JSON
{
"data": {
"heroe": {
"apellido": "Atrida"
}
}
}
La ventaja de GraphQL es que podemos navegar por la información asociada al héroe. Así, la siguiente consulta:
//consulta GraphQL
{
heroe(nombre: "Agamenon") {
posesiones{nombre}
}
}
devuelve el conjunto de ciudades sobre las que Agamenón reina:
//respuesta JSON
{
"data": {
"heroe": {
"posesiones": [
{
"nombre": "Argos"
},
{
"nombre": "Micenas"
}
]
}
}
}
Mientras que:
//consulta GraphQL
{
heroe(nombre: "Agamenon") {
posesiones{nombre divinidad{nombre}}
}
}
obtiene los nombres de las divinidades veneradas en las ciudades sobre las que reina el héroe Agamenón.
//respuesta JSON
{
"data": {
"heroe": {
"posesiones": [
{
"nombre": "Argos",
"divinidad": {
"nombre": "Hera"
}
},
{
"nombre": "Micenas",
"divinidad": {
"nombre": "Hera"
}
}
]
}
}
}
Los modificadores se definen de manera similar:
//esquema
type Mutation {
newHeroe(nombre: String!, apellido: String!) : Heroe!
}
Este modificador permite crear un nuevo Héroe en el sistema. Por ejemplo:
//consulta GraphQL
mutation {
newHeroe( nombre: "Diomedes", apellido: "Tidida" ) {
nombre
apellido
}
}
Nótese que también aquí hay que indicar los atributos de retorno (en el ejemplo, nombre y apellido), o de lo contrario saltará un error. Así, después de haber insertado el nuevo objeto en el sistema, nuestro servidor devolverá un JSON de este estilo:
//respuesta JSON
{
"data": {
"newHeroe": {
"nombre": "Diomedes",
"apellido": "Tidida"
}
}
}
Por cierto que, para facilitar el manejo de parámetros, se pueden definir tipos Input:
//esquema
input HeroeInput{
nombre: String!
apellido: String!
}
type Mutation {
newHeroe(heroe: HeroeInput!) : Heroe!
}
En este caso la consulta tendría esta forma:
//consulta GraphQL
mutation {
newHeroe(heroe: {nombre:"Diomedes", apellido:"Tidida"}){
nombre
apellido
}
}
Hemos expuesto brevemente el modo en que GraphQL define los esquemas y las consultas que nos proporcionarán flexibilidad a la hora de definir nuestras APIs. En una próxima entrada explicaremos cómo implementar un servidor GraphQL con ayuda de Spring Boot.
(Publicado previamente en https://profile.es/en/blog/es-graphql-una-alternativa-a-rest-parte-1/)
Top comments (0)