En este artículo les voy a explicar cómo podemos crear un API Rest con Quarkus que haga uso de un cache local, esto a diferencia de usar una solución de cache distribuida como Elasticache y que ejemplifiqué previamente.

La razón para esto es que hay casos de uso en nuestros desarrollos en donde no necesitamos que el cache sea distribuido, pero si beneficiarnos de las bondades de un cache en nuestros APIs.

El primer caso que debemos hacer es crear nuestro proyecto con la versión más reciente de Quarkus.

mvn io.quarkus:quarkus-maven-plugin:1.4.1.Final:create \
    -DprojectGroupId=dev.gerardo \
    -DprojectArtifactId=cache-api \
    -DclassName="dev.gerardo.boundary.DemoResource" \
    -Dpath="/demo"

Para este ejemplo, vamos a suponer que tenemos un servicio que consulta una base de datos y que la consulta que realiza es muy frecuente, cambia con poca frecuencia, pero también demora un tiempo importante en responder.

Procedemos a crear primero una clase que represanta las ventas que se han registrado por departamento en nuestra tienda, y por simplicidad tiene el código del departamento, su nombre y un monto total de ventas.

public class Ventas {
  private String codDepartamento;
  private String departamento;
  private BigDecimal monto;

}

Luego creamos un servicio que se conecta a una base de datos relacional y ejecuta la consulta, siguiendo con el ejemplo se va a llamar VentasService.

public class VentasService {
  private final Logger LOG =
        Logger.getLogger(VentasService.class.getName());

  public Ventas getResumenVentas(String codigo) {
	   LOG.log(Level.INFO, "Invocando la consulta en la BD");
	   //... ejecutamos una sentencia sql que trae las
     // ventas del día
	   return ventas;
	}
}

Ahora, vamos a extender nuestro recurso de pruebas para consumir este servicio de la siguiente manera:

@Inject
VentasService service;

@GET
@Path("/departamentos/{depto}")
@Produces(MediaType.APPLICATION_JSON)
public Ventas getVentas(@PathParam(value = "depto")
      String depto) {

    Ventas venta = service.getResumenVentas(depto);

    return venta;

}

Pero antes de probarlo, agregamos la extensión de resteasy para poder retornar nuestro objeto como un JSON.

mvn quarkus:add-extension -Dextensions="resteasy-jsonb

Si accesos el url del servicio en http://localhost:8080/demo/departamentos/01 obtendríamos esta respuesta del departamento 01

{
  "codDepartamento":"01",
  "departamento":"Ferretería",
  "monto":5000
}

Y en log notaremos la entrada que dice:

(executor-thread-126) Invocando la consulta en la BD

Hasta aquí, tenemos un servicio común y corriente. Para comenzar a usar el cache debemos agregar la siguiente extensión de Quarkus.

mvn quarkus:add-extension -Dextensions="cache"

Luego, vamos a nuestro servicio y colocamos la siguiente anotación

  @CacheResult(cacheName = "cache-ventas")
   public Ventas getResumenVentas(String codigo)

Con ella, lo que estamos indicando es se cree un cache con el nombre “cache-ventas” y que se retorne el valor almacenado en el cache(de existir) sin ejecutar el código de nuestro método.

Si ejecutamos de nuevo nuestro servicio, veremos en el log que la primera vez aparece:

(executor-thread-126) Invocando la consulta en la BD

Pero las invocaciones posteriores no registran esa entrada y obtenemos el resultado almacenado en el cache producto de la primera invocación.

¿Cómo sabe cuál es la llave del cache?

Bueno, la respuesta es que Quarkus calcula la llave del cache usando los argumentos del método que estamos decorando. En este caso por el parámetro codigo. Dicho eso, sería equivalente escribir el método con esta anotación adicional.

@CacheResult(cacheName = "cache-ventas")
public Ventas getResumenVentas(@CacheKey String codigo)

Si el método recibiera múltiples parámetros y nos interesa que calcule la llave del cache por algunas de ellas, entonces debemos usar esa anotación @CacheKey.

Ahora bien, qué pasa si debemos quitar explícitamente una entrada del cache. Siguiendo nuestro ejemplo, supongamos que tenemos un método que realiza una reversión en las ventas de un departamento. Ante esa situación, próximas ejecuciones del método getVentas deberían ir a la base de datos para traer la venta real.

En este caso usamos la anotación @CacheInvalidate.

 @CacheInvalidate(cacheName = "cache-ventas")
    public void aplicarReversiones(String codigo) {
        LOG.log(Level.INFO, "Invocando la reversion en la BD");
    }

Y si por otro lado necesitamos eliminar todos los contenidos del cache, podemos usar la anotación @CacheInvalidateAll, como ilustramos en este código de actualización general.

 @CacheInvalidateAll(cacheName = "cache-ventas")
    public void actualizacionGeneral() {
        .....
    }

Por supuesto, tenemos la manera de definir algunos parámetros del cache por medio de propiedades, como por ejemplo:

  • Indicar el tamaño máximo del cache quarkus.cache.caffeine.”cache-ventas”.maximum-size
  • El tiempo de expiración de una entrada luego de ser registrada en el cache quarkus.cache.caffeine.”cache-ventas”.expire-after-write, entre otros.

Conclusión


Espero que este artículo les muestre otra manera de poder usar un cache de una manera muy sencilla en nuestros servicios REST.