Tras una primera parte más teórica sobre GraphQL, es el momento de ver un ejemplo completo en el que pondremos en práctica todo lo explicado.

Hemos escogido la implementación de Java para GraphQL y un stack tecnológico muy extendido cuya base será Spring Boot, Maven 3.X y la versión 1.8 de Java. ¡Empezamos!

Stack tecnológico

A parte del framework base de Spring Boot, estas son las librerías relacionadas con GraphQL que se han utilizado en la implementación del ejemplo:

Graphql para Java

Graphql para Java es la implementación oficial en la que están basados todos los frameworks del ecosistema Java.

Graphql-apigen

Filosofía “Schema first”. Es el framework que, a partir de un esquema definido en GraphQL, nos generará un armazón para incluir nuestra programación de la obtención de los datos de sus diferentes orígenes.

Personalmente opino que la declaración de esquemas programáticamente nos obliga casi a aprendernos un API completo, es excesivamente “verboso”, complejo y apenas reutilizable. Para este tipo de declaración existen otras implementaciones en otros lenguajes más idóneos como NodeJS o Python.

Sus características:

Graphql-Spring-Boot

Graphql-Spring-Boot es el oficial para Spring Boot que proponen, no obstante, existe una aproximación muy buena también que sería viable Spring Boot starter Graphql.

Implementación de nuestro grafo

La estructuración del código podemos verla a continuación:

Identificamos las siguientes partes:

Resolviendo atributos

Como hemos explicado anteriormente, GraphQL toma cada field de forma independiente y en sus implementaciones incluye el concepto de “fetcher/resolver” para la obtención de la información.

En nuestro caso, en vez de fetchers, poseeremos resolvers. Las entidades podrán estar “resueltas” o “no resueltas”, en caso de que estén en este último estado las obtendremos de la fuente de datos que corresponda.

Alguien podría pensar que usando MongoDB, en el cual poseemos habitualmente la información embebida, estamos “trabajando” lo mismo que para obtener un field que todos los restantes de la entidad.

Es decir, si nos piden el ID en una consulta, obtendremos el documento completo; por lo que no necesitaríamos hacer más consultas para los fields restantes… Es un ejemplo de cómo separar el modelo de persistencia del diseño de nuestro grafo de forma independiente exactamente igual que si de un API REST se tratase.

java
<br />
public abstract class BaseResolver<T>  {</p>
<p> /**<br />
 * Resuelve la obtención de los datos con la implementación por defecto<br />
 *<br />
 * @param unresolvedList<br />
 * @return<br />
 */<br />
 public List<T> resolve(List<T> unresolvedList) {<br />
 List<T> resolvedList = new ArrayList<>();<br />
 /* Miramos si no es null y nos viene con valor para obtenerlo si es necesario */<br />
 if (!requireNonNull(unresolvedList).isEmpty()) {<br />
 for (T element : unresolvedList) {<br />
 if (element != null) {<br />
 resolvedList.add(processElement(element));<br />
 }<br />
 }<br />
 }<br />
 return resolvedList;<br />
 }</p>
<p> /**<br />
 * Comprobaremos si está resuelta la entidad y si no es así obtenemos de la persistencia el "completo"<br />
 *<br />
 * @param unresolverdEntity<br />
 * @return<br />
 */<br />
 private T processElement(final T unresolved) {<br />
 /* Obtenemos el elemto filtrado por lo que corresponda */<br />
 if (unresolved.getClass().equals(unresolvedClass())) {<br />
 T findElement = findById(unresolved);<br />
 return findElement;<br />
 }<br />
 return unresolved;<br />
 }</p>
<p> /**<br />
 * Clase a implementar para la obtencion de los elementos relacionados<br />
 *<br />
 * @param unresolved<br />
 * @return<br />
 */<br />
 protected abstract T findById(T unresolved);</p>
<p> /**<br />
 * Clase la cual indicará wue una entidad no está resuelta<br />
 *<br />
 * @return<br />
 */<br />
 protected abstract Class<?> unresolvedClass();</p>
<p>}<br />

Analizando el código, podemos observar cómo en el método “processElement” comprobamos si el elemento es necesario resolverlo. Si es así hacemos una llamada a obtenerlo por su ID.

La obtención por el ID es una estrategia adoptada por defecto, un ejemplo sería la obtención de la propiedad “model” de un “car” en cualquiera de las consultas que se intervenga.

java
<br />
@Component<br />
public class ModelResolver extends BaseResolver<Model> implements Model.Resolver {</p>
<p> @Autowired<br />
 ModelService modelService;</p>
<p> @Override<br />
 protected Model findById(Model unresolved) {<br />
 return modelService.findById(unresolved.getId());<br />
 }</p>
<p> @Override<br />
 protected Class<?> unresolvedClass() {<br />
 return Model.Unresolved.class;<br />
 }</p>
<p>}<br />

En el modelo se verá que, dentro de un CarMO, existe una referencia a ModelMO, la cual no se usa y se provoca la consulta independiente de este último como ejemplo claro, ya que siempre será marcado como “Unresolved” para que en la resolución del grafo se realice la llamada correspondiente.

Como vemos, una de las desventajas de GraphQL es la “cantidad” de accesos que se realizan a las fuentes de datos. Para ello propone la realización de cachés distribuidas, precisamente para evitar latencias y aplicando las políticas adecuadas en cada uno de los casos.

Consulta raíz

Como hemos aprendido siempre existirá una consulta raíz, a partir de la cual podremos consultar todo nuestro esquema según lo hayamos definido. En nuestro caso será:

El código que la identifica sería el siguiente:

java
<br />
@Component<br />
public class QueryRootImpl implements QueryRoot {</p>
<p> @Autowired<br />
 CarService carService;</p>
<p> @Autowired<br />
 BrandService brandService;</p>
<p> @Autowired<br />
 ModelService modelService;</p>
<p> @Override<br />
 public List<Car> getCars() {<br />
 return carService.findAll();<br />
 }</p>
<p> @Override<br />
 public Car car(final CarArgs args) {<br />
 return new Car.Unresolved(args.getId());<br />
 }</p>
<p> @Override<br />
 public List<Brand> brands(BrandsArgs args) {<br />
 return brandService.findAll();<br />
 }</p>
<p> @Override<br />
 public List<Model> models(ModelsArgs args) {<br />
 return modelService.findAll();<br />
 }</p>
<p> @Override<br />
 public Model model(ModelArgs args) {<br />
 return new Model.Unresolved(args.getId());<br />
 }</p>
<p> @Override<br />
 public Brand brand(BrandArgs args) {<br />
 return new Brand.Unresolved(args.getId());<br />
 }</p>
<p>}<br />

Si observáis es plenamente identificativo con respecto a la definición realizada en el esquema. Esto es de lo que más me gusta de la librería elegida para la integración e implementación.

Podemos distinguir dos tipos de métodos:

A parte, vemos que para obtenerlo por su ID marcamos la entidad como no resuelta (Unresolved) para que pase por el punto de obtención de la información.

Mutaciones de Car

Al igual que existe una consulta raíz para las operaciones, en las mutaciones el concepto es análogo, es decir, un punto de entrada donde podremos ver todas las operaciones existentes en nuestro GraphQL Server.

En nuestro caso dos operaciones sobre coches createCar y deleteCar.

La correspondencia en el código en este caso sería la siguiente:

java
<br />
@Component<br />
public class CarMutationImpl implements MutateCars {</p>
<p> @Autowired<br />
 CarService carService;</p>
<p> @Override<br />
 public Car createCar(CreateCarArgs args) {<br />
 return carService.createCar(args.getCar());<br />
 }</p>
<p> @Override<br />
 public String deleteCar(DeleteCarArgs args) {<br />
 return carService.deleteCar(args.getId());<br />
 }<br />
}<br />

Como podemos observar, tenemos un objeto para la entrada de los argumentos en cada una de las operaciones con los atributos definidos en nuestro esquema y realizaremos una llamada al servicio para hacer lo que corresponda.

Por ejemplo, en *createCar *vemos que nos retorna un coche una vez creado, dicho objeto entrará en el flujo de consulta normal.

Configuración y puesta en marcha del ejemplo

En este repositorio podréis encontrar todos los pasos para la configuración y puesta en marcha, pero deberéis tener configurado en vuestro entorno:

Conclusiones

Como se ha podido observar la implementación del esquema de GrahpQL, en Java se integra perfectamente con las herramientas que usamos en el día a día. Sin embargo, he de admitir que de primeras todas las lindezas que propone como novedosas transmiten complejidad y posible ineficiencia en la parte servidora si la implementación no es correcta.

La implementación con Java es más “verbosa”, más compleja y menos natural que con otros lenguajes de script como NodeJS (GraphQL-express-js) o Python (Graphene),ya que las convenciones de las librerías que lo implementan facilitan mucho el desarrollo. No obstante, en próximas entregas, mostraremos cómo se implementa el mismo ejemplo en estos lenguajes para que cada uno decida por sí mismo ;)

GraphQL ha llegado para quedarse como alternativa clara a las tecnologías que usamos habitualmente para la construcción de aplicaciones SPA, aplicaciones móviles… Aunque no sea la solución para todos los casos, no dudes en tenerlo en cuenta para tu próxima arquitectura.

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.