Java Quarkus: API REST Cache

Ejemplo Api REST con Cache, ORM y base de datos PostgreSql

Java Quarkus incorpora una extensión para almacenamiento en cache local bastante práctica para aplicaciones que no requieran cache distribuida, pero que necesiten mejoras de rendimiento y velocidad de respuesta.

En el archivo 'pom.xml' se definen las dependencias para la gestión del cache local, servicios REST Json, ORM Panache y el driver de base de datos (PostgreSQL):

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-cache</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>

En el archivo 'application.properties' se configuran los parámetros de tamaño inicial y maximo del cache, junto al tiempo de expiración de una entrada luego de ser registrada en el cache local.


quarkus.cache.caffeine."cache-users".initial-capacity=20
quarkus.cache.caffeine."cache-users".maximum-size=300
quarkus.cache.caffeine."cache-users".expire-after-write=300S

Agregamos una clase entidad sencilla (User) y una clase 'repositorio' para temas de persistencia:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue
    private Long id;

    @Column(unique = true)
    private String documentId;

    private String firstName;
    private String lastName;

    @Enumerated(EnumType.STRING)
    private Gender gender;

    private String emailAddress;
    private String phoneNumber;

    public User() {
    }

    // getters ..
    // setters ..

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

    public Optional<User> findByDocumentId(String documentId) {
        return find("documentId", documentId).firstResultOptional();
    }

    public Optional<User> findByEmail(String email) {
        return find("emailAddress", email).firstResultOptional();
    }
}

La clase encargada de implementar el servicio de cache tiene el siguiente aspecto:

@ApplicationScoped
public class UserService {

    private static final Logger log = Logger.getLogger(UserService.class);

    @Inject
    private UserRepository userRepository;

    @CacheResult(cacheName = "cache-users")
    public Optional<User> fetchUser(String documentId) {
        if (Optional.ofNullable(documentId).isEmpty()) return Optional.empty();

        log.infof("[x] find entity by documentId: %s", documentId);
        return userRepository.findByDocumentId(documentId.strip());
    }

    @Transactional
    @CacheInvalidate(cacheName = "cache-users")
    public boolean deleteUser(String documentId) {

        if (Optional.ofNullable(documentId).isEmpty()) return false;

        Optional<User> user = userRepository.findByDocumentId(documentId.strip());
        if (user.isPresent()) {
            log.infof("[x] delete entity with documentId: %s", documentId);
            return userRepository.deleteById(user.get().getId());
        }
        return false;
    }

}

La anotación '@CacheResult' indica el nombre del cache configurado en el archivo de propiedades ('cache-users'). Con esto el valor retornado sera el almacenado en el cache (de existir) sin ejecutar el código de nuestro método.

La anotación '@CacheInvalidate' se utiliza para quitar explícitamente una entrada del cache.

La clase que consume el servicio luce de la siguiente forma:

@Path("/")
public class UserRest {

    @Inject
    private UserService userService;

    @GET
    @Path("/api/v1/lab/users/{documentId}")
    public Response findByDocumentId(@PathParam String documentId) {
        Optional<User> user = userService.fetchUser(documentId);
        if (user.isPresent())
            return Response.ok(user.get()).build();

        return Response.status(Response.Status.NOT_FOUND).build();
    }

    @DELETE
    @Path("/api/v1/lab/users/{documentId}")
    public Response delete(@PathParam String documentId) {

        boolean delete = userService.deleteUser(documentId);
        if (delete)
            return Response.ok().build();

        return Response.status(Response.Status.NOT_FOUND).build();
    }

}

Puedes descargar el código fuente completo de este ejemplo en el siguiente enlace.




¿Tienes alguna consulta?
Puedes contactarme enviándome un mensaje desde aquí.