¿Qué son los recursos y los endpoints?
Un endpoint se puede describir brevemente como hacer que se ejecute código Java a consecuencia de hacer una determinada petición HTTP al servidor web de Quarkus. Los endpoints se van a agrupar en recursos o resources, que idealmente representarían todo un controlador (palabra familiar para aquellas personas que ya hayan trabajado con Spring, por ejemplo) que agrupa los distintos endpoints asociados a una tarea. Por ejemplo, en una API que se ocupe de gestionar libros, un recurso puede ser un Libro, y los distintos endpoints de este recurso podrían ser listar los libros, obtener un libro por su ID, buscar libros, crear un libro, actualizar un libro...
En Quarkus, crear un recurso es tan sencillo como agregar una clase a alguno de los paquetes que forman parte del proyecto. A esa clase la tendremos que anotar con la anotación @Path, que podemos encontrar en el paquete jakarta.ws.rs. Le pondremos como parámetro la raíz del recurso en la que queremos que se preste atención, por ejemplo, como se explica en el siguiente modo:
package quarkus;
import jakarta.ws.rs.Path;
@Path("/saludar")
public class EcoResource {
}
En este caso, EcoResource va a definir un controlador HTTP que está conectado con la ruta /saludar. Los endpoints que definamos dentro de esta clase se visitarán cuando se interactúe con la ruta /saludar a través del servidor HTTP.
Para crear un endpoint, todo lo que tenemos que hacer es agregar un método a una clase y anotarlo con alguna de las anotaciones que especifican el verbo HTTP que queremos usar. Lo enseño con un ejemplo y a continuación explico qué está pasando en este código:
package quarkus;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/saludar")
public class EcoResource {
@GET
public String saludar() {
return "Hola, como estás";
}
}
Esta clase, EcoResource, ahora tiene un método público denominado saludar() que devuelve un String. Como le hemos puesto la anotación GET al método, lo que ahora va a pasar es que si a nuestro servidor le lanzasemos una petición de tipo HTTP GET a /saludar, que son las coordenadas tanto del método como de la clase a la que pertenece, se invocaría el código Java de ese método. Es decir, los endpoints están conectados con un Path y con un verbo, y lanzan su código cuando se hace una petición sobre esa ruta.
La función saludar() devuelve un String. Si hemos incorporado un serializador como Jackson a nuestro proyecto de Quarkus, ese serializador interceptará la respuesta del método ejecutado, lo convertirá a un tipo de datos compatible y lo mandará como respuesta a la petición HTTP. ¿En qué se traducirá eso? En que si nuestro método devuelve un string, ahora ese string es el que se va a enviar al cliente como respuesta de la petición.
GET /saludar HTTP/1.1
Host: localhost:8080
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
content-length: 17
Hola, como estás
Cómo agregar múltiples endpoints a un recurso
Una clase que actúe de recurso o controlador puede hospedar múltiples endpoints. En un CRUD, con esto podríamos tener un endpoint para cada operación, como la operación listar, la operación recuperar, la operación insertar... Sin embargo, también podríamos tener múltiples endpoints para diferentes peticiones GET. En el siguiente ejemplo vamos a tener un total de cuatro operaciones diferentes en este recurso.
package quarkus;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/saludar")
public class EcoBasicResource {
// Aviso para copy and pasters: el siguiente código no funciona...
@GET
public String saludar() {
return "Hola, como estás";
}
@GET
public String dias() {
return "Hola, buenos días";
}
@GET
public String tardes() {
return "Hola, buenas tardes";
}
@GET
public String noches() {
return "Hola, buenas noches";
}
}
Excepto que este código no va a funcionar. Si tratamos de invocar el endpoint /saludar, obtendremos un error, porque Quarkus detectará que hay cuatro endpoints atendiendo al mismo endpoint HTTP, mismo verbo y, en general, mismas condiciones. No se permite tener más de un endpoint atendiendo exactamente el mismo tipo de ruta, tienen que cambiar en algo para evitar confusión.
Una cosa que podemos hacer es asignar un Path diferente a cada endpoint. @Path, además de permitir establecer la raíz de un recurso, también se puede usar para agregar sufijos a un endpoint específico. Si ponemos una anotación @Path en la clase y otra anotación @Path en el endpoint, la ruta efectiva de ese endpoint será el resultado de concatenar, primero el @Path de la clase, y luego el @Path del endpoint. Por ejemplo, en el siguiente caso la ruta efectiva del endpoint HolaMundoResource.holaMundo() sería /hola/mundo:
@Path("/hola")
public class HolaMundoResource {
@GET
@Path("/mundo")
public String holaMundo() {
return "Hola, como estás";
}
}
Aplicado a nuestro ejemplo anterior, podemos liquidar cualquier confusión si le ponemos un @Path diferente a cada uno de los endpoints.
package quarkus;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/saludar")
public class EcoBasicResource {
@GET
public String saludar() {
return "Hola, como estás";
}
@GET
@Path("/dias")
public String dias() {
return "Hola, buenos días";
}
@GET
@Path("/tardes")
public String tardes() {
return "Hola, buenas tardes";
}
@GET
@Path("/noches")
public String noches() {
return "Hola, buenas noches";
}
}
Nuestro recurso ahora nos aporta cuatro endpoints diferentes:
GET /saludar, que genera el mensaje Hola, como estás.GET /saludar/dias, que genera el mensaje Hola, buenos días.GET /saludar/tardes, que genera el mensaje Hola, buenas tardes.GET /saludar/noches, que genera el mensaje Hola, buenas noches.