En este artículo les escribiré acerca de los tres cambios más importantes que se introdujeron en Eclipse MicroProfile 3.0 y que fue liberado el 11 de Junio de este año.

Los tres API que cambiaron con respecto a MicroProfile 2.2 son los siguiente:

  • Health Check que pasa a la version 2.0
  • Metrics que pasa a la versión 2.0
  • Rest Client que pasa a la versión 1.3

Los dos primeros han introducido cambios que no son compatibles con MicroProfile 2.2; por lo cual, sí se va a migrar a esta versión, se hace necesario realizar ajustes a nuestro código.

Seguidamente les explicaré los cambios más importantes que se introdujeron.

Health Check 2.0

En este API se hicieron cambios importantes en la respuesta de un Health Checky que es incompatible con la versión 2.2. Además se agregó soporte para las anotaciones @Liveness y @Readiness y se marcaron como deprecated varias anotaciones, como por ejemplo @Health

Se podrían preguntar ¿Qué significa Liveness y Readiness?

En el contexto de soluciones de orquestación de microservicios, como Kubernetes, una sonda de Liveness se usa para determinar cuando se debe reiniciar un container. A manera de ilustración, una sonda de este tipo puede determinar que se tiene un deadlock y que aunque la aplicación esta corriendo está no hace progresos; o que está consumiendo un amplio porcentaje de memoria, u otras condiciones en donde reiniciar el contenedor puede permitir que la aplicación este disponible de nuevo. En síntesis, la aplicación no se va a recuperar a menos que la reiniciemos.

Por otro lado, una sonda de Readiness lo que permite saber es si el container esta listo para comenzar a recibir tráfico. Y esto es muy importante, pues en ocasiones nuestro servicio necesita cargar algún tipo de data antes de ser operativo o depende de otros servicios al arranque y este tipo de sonda nos permite manejar esas situaciones.

Ahora bien, a nivel de código el extracto de nuestro pom relevante sería el siguiente:

<dependency>
  <groupId>org.eclipse.microprofile</groupId>
  <artifactId>microprofile</artifactId>
  <version>3.0</version>
  <type>pom</type>
  <scope>provided</scope>
</dependency>

Ahora, para poder crear estas sondas vamos a hacer lo siguiente:

@Readiness
@ApplicationScoped
public class DemoReadinessCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.named("Readiness")
                .withData("Data 1", "Disponible").up().build();
    }
}

En este caso hacemos la anotación @Readiness y nuestra clase implementa HealthCheck y sobreescribimos el método call. En este ejemplo simplificado respondemos con un up y podemos agregar diferente información como parte del payload de respuesta.

Por otro lado, para la sonda de liveness podemos hacer lo siguiente.

@Liveness
@ApplicationScoped
public class DemoLivenessCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.named("Liveness")
                .withData("Dato 1", "Valor X")
                .withData("Dato 2", "Valor Y")
                .state(true).build();
    }
}

Como se ve, es posible tener clases separadas para cada una de las sondas o una sola clase que tenga ambas anotaciones.

Si ejecutamos nuestro servicio, veremos que se expone ahora de manera automática un url /health, y si lo accesamos tenemos la siguiente respuesta:

{
  "checks": [{
    "data": {
      "Data 1": "Disponible"
    },
    "name": "Readiness",
    "status": "UP"
  }, {
    "data": {
      "Dato 2": "Valor Y",
      "Dato 1": "Valor X"
    },
    "name": "Liveness",
    "status": "UP"
  }],
  "status": "UP"
}

Notarán que en el JSON se observa el resultado de ambas sondas; además podemos consultar cada una de manera independiente por medio del URL /health/live

{
  "checks": [{
    "data": {
      "Dato 2": "Valor Y",
      "Dato 1": "Valor X"
    },
    "name": "Liveness",
    "status": "UP"
  }],
  "status": "UP"
}

y /health/ready

{
  "checks": [{
    "data": {
      "Data 1": "Disponible"
    },
    "name": "Readiness",
    "status": "UP"
  }],
  "status": "UP"
}

Metrics 2.0

En este API los cambios más sustanciales tienen que ver con el manejo de todos los contadores. A partir de ahora todos ellos son de tipo monotonic y los que anteriormente eran non monotic pasan a ser de tipo ConcurrentGauges. Podemos decir -en nuestro contexto- que un contador es monotonic cuando incrementa un valor numérico, como por ejemplo las invocaciones a un método. El comportamiento de los contadores de la versión previa se logra con @ConcurrentGauges.

Por este y otros factores es que se dice que Metrics 2.0 implica cambios en el código y no es compatible con las versiones previas de MicroProfile.

Dicho esto, para poder tener métricas de nuestro servicio podemos hacer lo siguiente para contar con un contador:

@Counted(name = "conteoAccesos",
        absolute = true,
        description = "Numero de veces que es invocado el metodo acceso")
public void acceso() {
    // nuestra lógica
}

Y para crear uno del tipo Gauge sería de la siguiente manera:

@Gauge(unit = MetricUnits.NONE,
        name = "totalProductos",
        absolute = true,
        description = "Cantidad total de productos")
public int getTotal() {
      // nuestra lógica
      return unValor;
}

Con lo anterior, ya podemos tener métricas en el formato de OpenMetrics conocido anteriormente como formato Prometheus. En nuestro servicio ya tenemos un url /metrics provisto de manera automática y en donde presentamos un extracto de la información que nos brinda.

# TYPE base_jvm_uptime_seconds gauge
# HELP base_jvm_uptime_seconds Displays the start time of the Java virtual machine in milliseconds. This attribute displays the approximate time when the Java virtual machine started.
base_jvm_uptime_seconds 211.91
# TYPE base_classloader_unloadedClasses_total counter
# HELP base_classloader_unloadedClasses_total Displays the total number of classes unloaded since the Java virtual machine has started execution.
base_classloader_unloadedClasses_total 12
# TYPE application_conteoAccesos_total counter
# HELP application_conteoAccesos_total Numero de veces que es invocado el metodo acceso
application_conteoAccesos_total 15
# TYPE application_totalProductos gauge
# HELP application_totalProductos Cantidad total de productos
application_inventorySizeGuage 25

Observen que en la parte inferior están presentes las métricas de nuestro aplicativo. También podemos hacer uso del url /metrics/application para tener únicamente las métricas de nuestra aplicación.

# TYPE application_conteoAccesos_total counter
# HELP application_conteoAccesos_total Numero de veces que es invocado el metodo acceso
application_conteoAccesos_total 15
# TYPE application_totalProductos gauge
# HELP application_totalProductos Cantidad total de productos
application_inventorySizeGuage 25

Con este URL podemos alimentar a Prometheus y graficar nuestras métricas con herramientras como Grafana.

RestClient 1.3

Esta nueva versión nos permite:

  • Tener soporte SSL a través de métodos nuevos en RestClientBuilder y propiedades de configuración. Por ejemplo podemos hacer lo siguiente:
KeyStore myStore = leerKeyStore();
RestCientBuilder.newBuilder()
    .trustStore(myStore);

De esta forma podemos usar de una manera sencilla y trust store diferente del provisto por el JVM. Inclusive podemos definirlos por medio de propiedades de MicroProfile Config.

  • Permitir que los proxy client puedan ser Closeable/AutoClosable. Ahora podemos hacer que una instancia de un cliente puede ser cerrada al hacer un casting a Closeable o AutoCloseable si usamos un try con recursos, a modo de ilustración:
MiCliente client = RestClientBuilder.newBuilder()
  .baseUri(miUri)
  .build(MiCliente.class);

client.metodo();  // Un método que invocamos
((Closeable) client).close();  

o bien:

MiCliente client = RestClientBuilder.newBuilder()
  .baseUri(miUri)
  .build(MiCliente.class);

try (MiCliente x = client) {
  x.metodo();  // Un método que invocamos
}

  • Simplificación de configuración por medio de configKeys. En este caso podemos hacer uno de atributos de configuración para definir elementos como el URL, URI, scope, entre otros.
    @RegisterRestClient(configKey="miServicio")
    public interface MiCliente {
    @GET
    @Path("/metodo")
    Responde miMetodo();
    }
    

    Y las propiedades se conformarían como las siguiente:

miServicio/mp-rest/url
miServicio/mp-rest/uri
miServicio/mp-rest/scope
  • Y por último definir que MediaType default sea application/json cuando no tenemos ningún @Consumes o @Produces explícito.

Conclusión


Espero que este artículo les brinde un panorama general de las cosas nuevas que nos brinda MicroProfile 3.0; y que a la fecha tiene soporte en OpenLiberty y Thorntail V2.