Ejemplo de GraphQL con Quarkus

graphql-quarkus

graphql-quarkus


En entradas anteriores vimos un ejemplo de uso de GraphQL con Spring y DGS de Netflix, hoy en Ejemplo de GraphQL con Quarkus traemos el mismo ejemplo pero realizado íntegramente con Quarkus.

Para poder realizar este ejemplo, vamos a utilizar las librerías propias de Quarkus para la persistencia, como es Panache, y la librería de GraphQL en Quarkus, en lugar de DGS de Netflix como es SmallRye.

¿Qué es GraphQL?

GraphQL es un lenguaje que fue creado por Facebook que nos va a permitir consulta y manipulación de datos para API’s. Esta forma de realizar consultas y obtener respuestas nos va a permiritr obtener únicamente los campos que deseamos. De esta forma, en consultas masivas o respuestas con muchos datos podemos conseguir mostrar únicamente los campos necesarios.

Hands On

Para nuestro ejemplo vamos a usar una base de datos relacional en memoria con H2. Nuestro ejemplo consistirá en tres entidades muy básicas: Bank, User y Account Bank, en donde un usuario tiene una o N cuentas bancarias en un banco y un banco tiene 1 o N cuentas bancarias.

En nuestra aplicación haremos uso de Lombok para eliminar el boilerplate de Java.

Al igual que Spring Data nos proporciona JPA para facilitar el control sobre bases de datos, con Quarkus hacemos uso de Panache. Que através de Hibernate, nos ofrecará las ventajas de JPA y nos facilitará el desarrollo de nuestras entidades. Por lo que si ya estas familiarizado con Spring Data, Panache, guarda muchas similitudes y no debería suponer mucho esfuerzo entenderlo.

Puedes utilizar https://code.quarkus.io/, para generar la estructura de tu proyecto

Dependencias Maven para usar GraphQL en Quarkus

   <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-graphql</artifactId>
    </dependency>

La anterior dependencia, smallrye, nos proporciona la lógica necesaria para incorporar GraphQL a nuestra aplicación.

Recuerda que puedes usar el siguiente comando directamente desde tu terminal si ya tienes el proyecto creado:

quarkus:add-extension -Dextensions="graphql"

Dependencias Maven para usar persistencia en Quarkus

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-h2</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>

Como hemos comentado anteriormente, es necesario añadir la dependencia de panache para proporcionar persistencia a nuestra aplicación.

Schema GraphQL y Entidades de Dominio

En nuestro anterior ejemplo realizado con DGS de Netflix y Spring Boot, tuvimos que crear y añadir en la carpeta resources nuestros schemas de GraphQL. Pero en este caso con Quarkus y la librería smallrye, no es necesario la creación de los schemas, tan solo el modelo. Por lo que en este caso únicamente crearemos los objetos de dominio, para mostrar el funcionamiento. Aunque esta no es una buena estructura, sirve para hacer más entendible el ejemplo, este podría ser el típico caso para usar Arquitectura Hexagonal, ya que hemos cambiado la tecnología pero la lógica sigue siendo la misma.

Creación entidad User

Para la creación de la entidad hacemos uso de las anotaciones de lombox y de javax.

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue
    private UUID id;
    private String firstName;
    private String lastName;
    private int age;
    private String address;
    private String country;
    private String city;
    @OneToMany(mappedBy = "user")
    private Set<Account> accounts;
    @ManyToOne()
    private Bank bank;
}

Creación entidad Bank

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class Bank {
    @Id
    @GeneratedValue
    private UUID id;
    private String name;
    private String country;
    @OneToMany(mappedBy = "bank")
    private Set<User> users;
    @OneToMany(mappedBy = "bank")
    private Set<Account> accounts;
}

Construccion de la entidad account

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Account {
    @Id
    @GeneratedValue
    private UUID id;
    private String name;
    private String alias;
    private BigDecimal amount;
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    private Bank bank;
}

Creación Objetos Mutation o Input

Cuando trabajamos con GraphQL vamos a definir una serie de objetos como los input a nuestra aplicación, en este caso vamos a definir una serie de objetos que serán los que se guarden en base de datos. Es decir, necesitamos datos de entrada a nuestra aplicación. Los datos que se definen a continuación serán los datos que se guardarán.

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class UserInput {
    private String firstName;
    private String lastName;
    private int age;
    private String address;
    private String country;
    private String city;
}

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class BankInput {
    private String name;
    private String country;
}

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class AccountInput {
    private String name;
    private String alias;
    private BigDecimal amount;
    private UUID userId;
    private UUID bankId;
}

Una vez hemos finalizado todos los objetos de dominio y los input es momento de crear la capa repository que será realizada con PanacheRepository.

Creación de los Repositorios con PanacheRepository

Haciendo uso de PanacheRepository vamos a crear la capa Repository. Esta interfaz implementa métodos de consulta y de guardado.

@ApplicationScoped
public class UserRepository implements PanacheRepository<User> {
}

@ApplicationScoped
public class BankRepository implements PanacheRepository<Bank> {
}

@ApplicationScoped
public class AccountRepository implements PanacheRepository<Organization> {
}

Querys y Mutations con GraphQLApi en Quarkus

Una vez hemos finalizado la creación de las entidades y del repositorio llega el momento de añadir la lógica necesaria para poder realizar consultas y mutations con GraphQL en Quarkus. Para ello, vamos a hacer uso de las siguientes anotaciones:

  • @GraphQLApi, esta anotación al principio de la clase indica que será un bean de tipo endpoint en GrapqhQL.
  • @Query, indica que este método será una consulta.
  • @Mutation, añadido en un método indica que esa petición es una
  • @Name, esta anotación será para aquellos métodos que lleven parámetros de tipo input.

Query en GraphQL con Quarkus

A continuación veremos como realizar consultas para el schema account.

@GraphQLApi
@RequiredArgsConstructor
public class AccountQuery {

    private final AccountRepository repository;

    @Query("accounts")
    public List<Account> findAll() {
        return repository.findAll().stream().collect(Collectors.toList());
    }

    @Query("account")
    public Account findById(@Name("id") Long id) {
        return repository.findById(id);
    }
}

Esta clase, en la que vamos a hacer dos consultas diferentes, la hemos marcado con @GraphQLApi, para indicar que en una case de Query de GraphQL, luego a continuación hemos añadido en los métodos encargados de hacer la query la anotación @Query y con el nombre que se hará la query.

Mutation en GraphQL con Quarkus

El tipo Mutation en GraphQL es aquel que se va a encargar de hacer un guardado o una petición que puede cambiar el estado, por ejemplo lo que sería en REST, un POST, un PATCH o un PUT.

Para poder hacer mutation en GraphQL vamos a añadir en la clase @GraphQLApi, y en cada método que se encarge de hacer mutation añadiremos @Mutation(«nombre»), veamos un ejemplo:

@GraphQLApi
@RequiredArgsConstructor
public class AccountMutation {

    private final AccountRepository accountRepository;

    private final BankRepository bankRepository;

    private final UserRepository userRepository;

    @Mutation("createAccount")
    @Transactional
    public Account createAccount(@Name("account") AccountInput account) {
        User user = userRepository.findById(account.getUserId());
        Bank bank = bankRepository.findById(account.getBankId());

        var accountToSave = new Account(null,
                account.getName(), account.getAlias(), account.getAmount(), user, bank);
        accountRepository.persistAndFlush(accountToSave);
        return accountToSave;
    }
}

Hay que tener en cuenta que en el método hemos añadido @Name, porque estamos añadiendo un input, es decir, queremos guardar una nueva cuenta.

Probando GraphQL con Quarkus

Una vez hemos generado nuestro código, lo primero que vamos a hacer, es ejecutar nuestra aplicación Quarkus.

mvn compile quarkus:dev

Si todo funciona bien deberíamos ver algo así:

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2021-05-08 16:42:23,139 INFO  [io.quarkus] (Quarkus Main Thread) graphql-quarkus 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.3.Final) started in 1.023s. Listening on: http://localhost:8080
2021-05-08 16:42:23,140 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-05-08 16:42:23,141 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-h2, mutiny, narayana-jta, smallrye-context-propagation, smallrye-graphql]
2021-05-08 16:42:23,141 INFO  [io.qua.dep.dev.RuntimeUpdatesProcessor] (vert.x-worker-thread-7) Hot replace total time: 2.384s 

GraphQl con Quarkus arranca en el endpoint http://localhost:8080/q/graphql-ui/, con lo que si accedemos a ese endpoint veremos la interfaz de GraphQL para hacer consultas:

Interfaz de GraphQL en Quarkus | Ejemplo de GraphQL con Quarkus
Interfaz de GraphQL en Quarkus

Vamos a lanzar una cuantas consultas para ver su funcionamiento, empezaremos con un mutation ya que es necesario primero guardar un bank.

mutation CREATE {
   createBank(bank:
      {name: "Santander", country:"Spain"}) {
        id
        name
        country
      }   
}
Respuesta mutation con GraphQL y Quarkus | Ejemplo de GraphQL con Quarkus
Respuesta mutation con GraphQL y Quarkus

Una vez hemos guardado un bank vamos a hacer una petición para recuperar todos:

{
 banks{
  id
  name
  country
 }
}
 

Con la query anterior vamos a mostrar todos los banks que hay en nuestra base de datos, en nuestro caso solo una. Los campos que aparecerán en la búsqueda serán aquellos que se indiquen en la query, en este caso vamos a mostrar id, name y country.

Query con GraphQL en Quarkus
Query con GraphQL en Quarkus

y finalmente vamos a probar a hacer una búsqueda de un bank por id.

{
 bank(id:1){
  id
  name
  country
 
 }
}
 
Query por Id con GraphQL y Quarkus
Query por Id con GraphQL y Quarkus

Ahora vamos a volver a hacer la misma Query pero añadiendo menos campos en la consulta.

{
 bank(id:1){
  id
 
 }
}
Consulta mostrando un solo campo
Consulta mostrando un solo campo

En la consulta anterior solo se añade el id en la query, por lo que en la respuesta solo llevará ese campo.

Como hemos comentado casi al principio del artículo, al hacer uso de la librería smallrye, se autogenera los schemas de graphQL sin que sea necesario su implementación. Si quieres ver los schemas generados puedes acceder al endpoint http://localhost:8080/graphql/schema.graphql, para los tipos generados.

Conclusión de Ejemplo de GraphQL con Quarkus

Al igual que pasa con la librería de Netflix DGS, la librería para Quarkus SmallRye, nos proporciona un API para poder utilizar GraphQL en Quarkus mediante anotaciones.

GraphQL ha llegado como un buen sustituto para todas las aplicaciones y arquitecturas que funcionan con servicios REST, aportándo muchas mejoras y facilidades a cualquier API, alguna de esas mejoras la hemos podido ver en nuestro ejemplo. Esto unido a un framework como Quarkus cuya principal ventaja es que se encuentra diseñado para entornos Cloud, hace de ambos una combinación perfecta para crear una buena API.

Si quieres ver el ejemplo completo puedes verlo en nuestro github.

No te olvides de seguirnos en nuestras redes sociales Facebook o Twitter para estar actualizado.


Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *