Graphql + Spring Boot

  • Graphql + Spring Boot
    • Breve introducción a Spring Boot
    • Breve introducción a Graphql
    • Entremos en harina
      • Sintaxis básica
      • Implementación con Java + Spring Boot
        • Dependencias
        • Implementación de resolvers
    • Conclusión y líneas futuras

En este artículo vamos a explicar cómo implementar un API que gestionará un conjunto de tarjetas (cards) y
cómo realizará todas estas tareas publicándolo con GraphQL.

Este es un ejemplo un poco más elaborado que el típico HolaMundo con GraphQL, pero sin ser excesivamente complejo como para que nos desviemos del propósito de la PoC: ver hasta donde llega una solución basada en GraphQL.

En general, vas a encontrar infinidad de ejemplos sencillos con clientes front de Graphql. Empiezas a leer y enseguida ves múltiples posibilidades y piensas que todo lo que sabes de ReST está viejo y huele a rancio. Con este artículo no queremos decir que ReST haya muerto, no somos tan insensatos como para realizar semejante afirmación, pero sí que queremos saber qué implica ofrecer un API a través de GraphQL, qué herramientas existen en el mundo Java y concretamente qué ayudas podemos tener a la hora de implementarlo con Spring Boot.

Breve introducción a Spring Boot

Spring Boot es un framework que ya lleva unos cuantos años con nosotros. En el momento de escribir estas líneas han liberado ya la versión 2.0.1 y ya soporta Spring 5 como core, webflux y convierte al framework en un framework reactivo.

Los rumores cuentan que el nacimiento de este proyecto fue un "a que no te atreves" (personalmente, supongo que usarían otras palabras, pero… ).

¿A que no te atreves a crear un servicio ReST que implemente un HolaMundo, con su servidor y con todo, en el espacio que ocupa un tweet?

Esto era un reto gigantesco cuando los tweet tenían una limitación de 140 caracteres.

Si nos ponemos en situación, ya veníamos conociendo Spring y nos gustaba, como un contenedor de beans, encargado de la inversión de control, etc. Pero no nos engañemos, montar toda la infraestructura de spring, era largo, aburrido y sobre todo, siempre igual: el servlet, el servlet mapping, los application_context, los doscientos millones de xmls, luego cambiamos xml por anotaciones, los @Bean, los @Configuration, etc, etc. Luego, empaqueta todo en un war, configura el server.xml de tomcat para publicarlo, oh! wait!… que era jetty, configura el jetty.xml, despliega...

Con Spring Boot todo este trabajo se simplifica. En la construcción de la aplicación, va analizando las dependencias que tenemos definidas y va asumiendo distintas configuraciones. De esta forma, todo el trabajo aburrido se resuelve de forma sencilla y si necesitamos modificar algún valor, en el 95% de las ocasiones, hay una property para definirla a nuestro gusto.

Esto hace que nos centremos en lo que realmente nos preocupa, que es desarrollar nuestra aplicación, dejando a un lado la "fontanería" necesaria para que funcione. 

Breve introducción a GraphQL

A su vez, GraphQL intenta resolver uno de los problemas que tiene ReST. En ocasiones, como estamos definiendo los recursos de forma independiente, obligamos a realizar múltiples llamadas para recuperar una estructura de información que en un contexto dado tiene relación. Además que cuando consultamos un recurso nos encontramos con una respuesta demasiado larga y que tengamos que ignorar los campos que no vayamos a utilizar específicamente. Esta situación es mucho más común de lo que nos podemos imaginar.

GraphQL lo inventaron los chicos de Facebook, allá por el año 2015, para reducir este sobrecoste de llamadas al back y poder delegar la composición y solicitud de campos a la parte front.

La idea es interesante, pero como todo, hay que usarla con cabeza.

Ofrecer al consumidor la posibilidad de filtrar únicamente los campos que necesita, es una opción muy acertada. Y más si tenemos en cuenta que ahorramos tiempos justo en la parte más lenta del proceso: la red. Estamos acostumbrados a tener un ancho de banda más grande que el túnel del Canal de la Mancha, pero ¿qué sentido tiene que para pintar nombre+apellidos tengamos que consumir un json de 300 campos?

Esta es una de las principales situaciones que pretende resolver esta tecnología.

Entremos en harina

El primer paso que tenemos que dar es el de definir el API. Es decir, definir el contrato de información que va a ser intercambiada entre la parte front de la aplicación y la parte back. En general, este paso es una muy buena práctica en cualquier diseño que se haga, pero con GraphQL es más interesante porque a partir de estos graphqls podremos ir realizando la implementación concreta.

Los ficheros graphqls tienen una sintaxis concreta y específica de GraphQL, por lo que son 100% independientes de la implementación concreta que se realice. De esta forma, se podrían reutilizar si la implementación es en NodeJS, Python o como es nuestro caso Java con Spring Boot. 

 

Sintaxis básica

En GraphQL se definen tres elementos principales, de tal forma que se pueda definir toda nuestra API usándolos.

  • Type / Input:  este elemento es el que se utiliza para definir elementos de entrada y de salida de nuestro API. Son los objetos que representan el dominio concreto que estamos modelando. La diferencia principal entre type e input es que el primero define la estructura de datos que se generará como respuesta a una consulta y el segundo define los datos de entrada que serán utilizados por las query y por las mutation.

type Card {        

     id: String!        

     name: String        

     epicId: String        

     sliceId: String        

     pos: Int        

     details: String        

     colour: String    

Como podemos ver, se define el nombre de la entidad de dominio y una serie de atributos con el tipo que lo representa. Con esta sintaxis, se pueden definir tipos básicos definidos por GraphQL, tipos compuestos definidos por nosotros, listas (representadas como []), atributos obligatorios (marcados con !), etc. Para ver la definición completa de los tipos definidos, te aconsejamos que leas lo que dice la especificación directamente.

  • Query: este elemento es el que definirá todas las funciones de acceso que estarán disponibles en nuestra API. Serían los equivalentes a los GET que definíamos en ReST. Se definen como una lista de operaciones con un valor de retorno asociado.

 type Query {     

     allBoards: [Board]!      

     howManyBoards: Long!   

 } 

  • Mutation: por último, las mutaciones es la forma que tiene GraphQL de definir las operaciones que se pueden hacer con los recursos. Si recordamos, de ReST utilizábamos los verbos HTTP para indicar qué operación se iba a ejecutar. En este caso, a pesar de que las distintas RFCs del protocolo HTTP indican para qué se deberían utilizar estos verbos, siempre había espacio a la interpretación. Y si había espacio a la interpretación enseguida empiezan los flames. Uno de los más grandes que han existido y que siguen existiendo es el de POST vs PUT vs PATCH. Según con quien habléis, os dará una versión de para qué utilizar cada verbo y qué aporta uno frente a otro. Con GraphQL esto se simplifica, ya que se define un nombre legible y descriptivo con la operación que se desea realizar.

type Mutation {      

     createCard(card: CardInput!): Card      

     updateCard(id: String!, card: CardInput): Card      

     deleteCard(id: String!): Card 

  } 

  • Resolvers:  con esta nomenclatura es como define GraphQL a las piezas encargadas de resolver la información solicitada. Podríamos verla como el servicio encargado de recuperar la información de donde sea necesario. Estas piezas no se definen en el schema, si no que es ya una pieza en un lenguaje. 

Para nuestra PoC ha sido suficiente con definir estos elementos simples, pero la especificación indica muchas más cosas interesantes: fragments, unions, variables, validaciones y un largo etcétera. Por este motivo, es interesante tener a mano la documentación completa y ver cómo utilizarla. 

 

Implementación con Java + Spring Boot

Dependencias

Primero tendremos que añadir una serie de dependencias a nuestro proyecto. En nuestra PoC añadimos al pom.xml las siguientes:

<dependency>       

      <groupId>com.graphql-java</groupId>         

     <artifactId>graphql-spring-boot-starter</artifactId>        

      <version>${graphql.version}</version>     

</dependency> 

La función principal de esta dependencia es únicamente la de definir el endpoint que atenderá todas las peticiones que se hagan a nuestro servidor. Por defecto está mapeando a /graphql, aunque se puede modificar desde nuestro application.yml

<dependency>         

     <groupId>com.graphql-java</groupId>         

     <artifactId>graphql-java-tools</artifactId>         

     <version>${graphql-java-tools.version}</version>     

</dependency>

Esta dependencia es la encargada de definir una serie de interfaces que tendremos que implementar, y se encarga de configurar todas las piezas relativas a GraphQL.

Por último, podemos definir una interfaz web para probar nuestro servicio, así como consultar todas la mutaciones y queries que tenemos disponibles. Esta interfaz está contemplada por GraphQL y se llama GraphiQL. Para tenerla disponible bajo el end-point /graphiql únicamente es necesario añadir a nuestro pom.xml la dependencia.

<dependency>         

     <groupId>com.graphql-java</groupId>         

     <artifactId>graphiql-spring-boot-starter</artifactId>       

      <version>${graphql.version}</version>   

</dependency>

Simplemente con esta configuración ya podemos arrancar nuestra aplicación Spring boot y, como mínimo,  podremos ver el IDE de GraphiQL. Naturalmente no será operativo, porque no hay ningún resolver de GraphQL correctamente configurado, pero podremos ver qué aspecto tiene.

graphql spring boot

 

Implementación de resolvers

Una vez que ya tenemos toda la "infraestructura" definida, es el momento de convertir la definición que tenemos del API en distintos graphqls en código java. Para ello, tendremos que dar una implementación a los distintos type, input, query y mutations que hayamos definido. Podemos empezar con los type y con los input, que son los elementos básicos de nuestro dominio. El proceso es sencillo, ya que son simples DTOs. Únicamene tenemos que traducir los tipos básicos del schema al tipo básico equivalente en java, y los objetos en distintas clases que los representen.

El siguiente paso es dar implementación a las mutations que hayamos definido. Para ello, es suficiente con
implementar la clase GraphQLMutationResolver y definir los métodos tal y como los hayamos definido en nuestro schema.

@Component
public class CardMutationResolver implements GraphQLMutationResolver{
     public Card updateCard(String id, CardInput card){
          [...]
}

Es muy interesante tener en cuenta cómo realiza la asociación entre el schema y la implementación. Lo hace por introspección, cargando todos los beans por orden alfabético y buscando en este orden una posible
coincidencia:

  1. Busca un método tal cual la misma signatura del schema en el bean.
  2. Busca un método con el prefijo get.
  3. Si no encuentra nada, falla el arranque y muestra una traza con los intentos que ha hecho para localizar la implementación.

Y por último nos queda definir una implementación a las query. Para ello, simplemente tenemos que implementar la clase GraphQLQueryResolver y definir los métodos correspondientes tal y como ya hemos hecho para las mutaciones.

@Component
public class QueryResolver implements GraphQLQueryResolver{
     public Iterable<Board> allBoards(){
          [...]
     }
}

Una vez que hemos definido todas las piezas, podremos comprobar cómo funciona a través de GraphiQL.
Podremos realizar las peticiones que necesitamos y además, podremos ir navegando por el modelo para
descubrir qué información podemos solicitar.

Conclusión y líneas futuras

Después de lo que acabamos de aprender, estamos en disposición de montar cualquier API utilizando GraphQL. Lo importante es saber discernir cuándo es más útil montar un API ReST clásica y cuando nos puede venir bien montar un GraphQL.

  • La potencia que ofrece a la hora de seleccionar los campos que nos interesan, es muy relevante.
    Históricamente esta selección ha sido compleja y siempre hacía el código muy rebuscado. Lo hemos
    implementado con filtros queryString, pero luego ensuciaba bastante el desarrollo back.
  • Puede ser importante tener en cuenta que no en todos los sitios nos dejan jugar con todos los verbos de
    http para definir nuestras APIs. GraphQL usa únicamente POST, así que podemos aprovechar esta
    característica para poder dar semántica a nuestras APIs. También acepta GET, pero como la query se
    tiene que enviar como queryString no lo consideramos una opción aceptable.
  • El planteamiento de tener un schema que define nuestra API, y que sea algo necesario es un punto muy a favor. El motivo es que nos orienta fuertemente a que definamos primero el contrato entre las partes para implementarlo posteriormente.

Por último, y para concluir, sería muy interesante poder auto-generar los DTOs a partir de los schemas y definir
una serie de interfaces a partir de las mutaciones y queries. Es este punto, en el que se podría simplificar más
aún toda esta implementación.

 

Últimos posts