Implementación de microservicios con Quarkus y MicroProfile

Conclusiones clave

  • Quarkus es un marco Java nativo de Kubernetes de pila completa creado para máquinas virtuales Java (JVM) y compilación nativa.
  • Al desarrollar la arquitectura de microservicios, hay algunos desafíos nuevos que deben abordarse, como la escalabilidad, la seguridad y la observabilidad.
  • Los microservicios proporcionan una lista de preocupaciones transversales para implementar correctamente los microservicios.
  • Kubernetes es un buen comienzo para implementar estas microservicilidades, pero hay algunas lagunas.
  • MicroProfile es una especificación para implementar preocupaciones transversales utilizando el lenguaje de programación Java.
  • La autenticación entre los servicios internos se puede lograr mediante el uso de tokens (es decir, JWT).

¿Por qué microservicilidades?

En una arquitectura de microservicios, una aplicación está formada por varios servicios interconectados donde todos trabajan juntos para producir la funcionalidad empresarial requerida.

Entonces, una arquitectura de microservicios empresarial típica se ve así:

Al principio, puede parecer fácil implementar una aplicación con una arquitectura de microservicios.

Pero hacerlo correctamente no es un viaje fácil, ya que hay algunos desafíos nuevos que no estaban presentes con una arquitectura monolítica.

Algunos de estos son tolerancia a fallas, descubrimiento de servicios, escalado, registro y seguimiento, solo por mencionar algunos.

Para resolver estos desafíos, cada microservicio debe implementar lo que en Red Hat hemos denominado “Microservicilidades”.

El término se refiere a una lista de inquietudes transversales que un servicio debe implementar además de la lógica empresarial para resolver estas inquietudes, como se resume en el siguiente diagrama:

La lógica empresarial se puede implementar en cualquier lenguaje (Java, Go, JavaScript) o en cualquier marco (Spring Boot, Quarkus) pero en torno a la lógica empresarial, se deben implementar las siguientes preocupaciones:

API : se puede acceder al servicio a través de un conjunto definido de operaciones de API. Por ejemplo, en el caso de las API web RESTful, HTTP se utiliza como protocolo. Además, la API se puede documentar utilizando herramientas como Swagger .

Descubrimiento : los servicios necesitan descubrir otros servicios.

Invocación : después de que se descubre un servicio, debe invocarse con un conjunto de parámetros y, opcionalmente, devolver una respuesta.

Elasticidad : una de las características importantes de una arquitectura de microservicios es que cada uno de los servicios es elástico, lo que significa que se puede escalar hacia arriba o hacia abajo de forma independiente dependiendo de algunos parámetros como la criticidad del sistema o según la carga de trabajo actual.

Resiliencia : en una arquitectura de microservicio, debemos desarrollar teniendo en cuenta las fallas, especialmente cuando nos comunicamos con otros servicios. En una aplicación monolítica, la aplicación, en su conjunto, está activada o desactivada. Pero cuando esta aplicación se divide en una arquitectura de microservicio, la aplicación se compone de varios servicios y todos están interconectados por la red, lo que implica que algunas partes de la aplicación pueden estar ejecutándose mientras que otras pueden fallar. Es importante contener la falla para evitar propagar el error a través de los otros servicios. La resiliencia (o resiliencia de la aplicación) es la capacidad de una aplicación / servicio para reaccionar a los problemas y aun así brindar el mejor resultado posible.

Canalización : un servicio debe implementarse de forma independiente sin ningún tipo de orquestación de implementación. Por esta razón, cada servicio debe tener su propia canalización de implementación.

Autenticación : uno de los aspectos clave con respecto a la seguridad en una arquitectura de microservicios es cómo autenticar / autorizar llamadas entre servicios internos. Los tokens web (y los tokens en general) son la forma preferida de representar reclamaciones de forma segura entre los servicios internos.

Registro : el registro es simple en aplicaciones monolíticas, ya que todos los componentes de la aplicación se ejecutan en el mismo nodo. Los componentes ahora se distribuyen en varios nodos en forma de servicios, por lo tanto, para tener una vista completa de los rastros de registro, se requiere un recopilador de datos / sistema de registro unificado.

Monitoreo : medir el rendimiento de su sistema, comprender el estado general de la aplicación y alertar cuando algo anda mal son aspectos clave para mantener una aplicación basada en microservicios funcionando correctamente. El seguimiento es un aspecto clave para controlar la aplicación.

Seguimiento : el seguimiento se utiliza para visualizar el flujo de un programa y la progresión de los datos. Eso es especialmente útil cuando, como desarrollador / operador, necesitamos verificar el recorrido del usuario a través de toda la aplicación.

Kubernetes se está convirtiendo en la herramienta de facto para implementar microservicios. Es un sistema de código abierto para automatizar, orquestar, escalar y administrar contenedores.

Solo tres de las diez microservicidades están cubiertas cuando se usa Kubernetes.

El descubrimiento se implementa con el concepto de un servicio de Kubernetes . Proporciona una forma de agrupar los pods de Kubernetes (actuando como uno solo) con una IP virtual estable y un nombre DNS. Descubrir un servicio es solo una cuestión de realizar solicitudes utilizando el nombre del servicio de Kubernetes como nombre de host.

La invocación de servicios es fácil con Kubernetes, ya que la propia plataforma proporciona la red necesaria para invocar cualquiera de los servicios.

La elasticidad (o escalamiento) es algo que Kubernetes tenía en mente desde el principio; por ejemplo 

kubectl scale deployment myservice --replicas=5 command
, en ejecución , la implementación de myservice escala a cinco réplicas o instancias. La plataforma Kubernetes se encarga de encontrar los nodos adecuados, implementar el servicio y mantener la cantidad deseada de réplicas en funcionamiento todo el tiempo.

Pero, ¿qué pasa con el resto de microservicilidades? Kubernetes solo cubre tres de ellos, entonces, ¿cómo podemos implementar el resto de ellos?

Hay muchas estrategias a seguir dependiendo del lenguaje o marco utilizado, pero en este artículo veremos cómo implementar algunas de ellas usando Quarkus .

¿Qué es Quarkus?

Quarkus es un marco Java nativo de Kubernetes de pila completa creado para máquinas virtuales Java (JVM) y compilación nativa, que optimiza Java específicamente para contenedores y le permite convertirse en una plataforma eficaz para entornos sin servidor, en la nube y Kubernetes.

En lugar de reinventar la rueda, Quarkus utiliza marcos de trabajo conocidos de nivel empresarial respaldados por estándares / especificaciones y los hace compilables en un binario mediante GraalVM .

¿Qué es MicroProfile?

Quarkus se integra con la especificación MicroProfile trasladando el ecosistema empresarial Java a la arquitectura de microservicios.

En el siguiente diagrama, vemos todas las API que componen la especificación MicroProfile. Algunas API, como CDI, JSON-P y JAX-RS, se basan en la especificación Jakarta EE (anteriormente Java EE). El resto fue desarrollado por la comunidad Java.

Implementemos API, invocación, resiliencia, autenticación, registro, monitoreo y rastreo de microservicidades usando Quarkus.

Cómo implementar microservicidades con Quarkus

Empezando

La forma más rápida de comenzar a usar Quarkus es a través de la página de inicio agregando las dependencias requeridas. Para este ejemplo, las siguientes dependencias están registradas para cumplir con los requisitos de microservicilidades:

  • API: RESTEasy JAX-RS, RESTEasy JSON-B, OpenAPI
  • Invocación: REST Client JSON-B
  • Resiliencia: tolerancia a fallas
  • Autenticación: JWT
  • Registro: GELF
  • Monitoreo: métricas micrométricas
  • Rastreo: OpenTracing

Podemos seleccionar manualmente cada una de las dependencias, o navegar hasta el siguiente enlace Microservicility Quarkus Generator donde se seleccionan todas. Luego presione el botón Generar su aplicación para descargar el archivo zip que contiene la aplicación andamiaje.

El servicio

Para este ejemplo, se genera una aplicación muy simple con solo dos servicios. Un servicio, denominado servicio de calificación , devuelve las calificaciones de un libro determinado, y otro servicio, denominado servicio de libros , devuelve la información de un libro junto con sus calificaciones. Todas las llamadas entre servicios deben estar autenticadas.

En la siguiente figura, vemos una descripción general del sistema completo:

El servicio de clasificación ya está desarrollado y proporcionado como un contenedor de Linux. Inicie el servicio en el puerto 9090 ejecutando el siguiente comando:


docker run --rm -ti -p 9090:8080
quay.io/lordofthejars/rating-service:1.0.0

Para validar el servicio, realice una solicitud a http: // localhost: 9090 / rate / 1


curl localhost:8080/rate/1 -vv

> GET /rate/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< www-authenticate: Bearer {token}
< Content-Length: 0

El código de estado devuelto se 

401 Unauthorized
debe a que no se proporcionó información de autorización en la solicitud en forma de token de portador (JWT). Solo los tokens válidos con grupo 
Echoer
 pueden acceder al servicio de clasificación .

API

Quarkus utiliza la conocida especificación JAX-RS para definir API web RESTful. Bajo las sábanas, Quarkus usa la implementación RESTEasy trabajando directamente con el marco Vert.X sin usar la tecnología Servlet.

Definamos una API para el servicio de libros implementando las operaciones más comunes:


import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

@Path("/book")
public class BookResource {

   @GET
   @Path("/{bookId}")
   @Produces(MediaType.APPLICATION_JSON)
   public Book book(@PathParam("bookId") Long bookId) {
    // logic
   }

   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   public Response getBook(Book book) {
       // logic

       return Response.created(
                   UriBuilder.fromResource(BookResource.class)
                     .path(Long.toString(book.bookId))
                     .build())
               .build();
   }

   @DELETE
   @Path("/{bookId}")
   public Response delete(@PathParam("bookId") Long bookId) {
       // logic

       return Response.noContent().build();
   }

   @GET
   @Produces(MediaType.APPLICATION_JSON)
   @Path("search")
   public Response searchBook(@QueryParam("description") String description) {      
       // logic

       return Response.ok(books).build();
   }
}

Lo primero que hay que tener en cuenta es que se definen cuatro puntos finales diferentes:

  • GET /book/{bookId}
    utiliza el método GET HTTP para devolver la información del libro con su calificación. El elemento de retorno se deshace automáticamente de JSON.
  • POST /book
    utiliza el método POST HTTP para insertar un libro que viene como contenido del cuerpo. El contenido del cuerpo se calcula automáticamente de JSON a un objeto Java.
  • DELETE /book/{bookId}
     utiliza el método DELETE HTTP para eliminar un libro por su ID.
  • GET /book/search?description={description}
     busca libros por su descripción.

La segunda cosa a tener en cuenta es el tipo de retorno, a veces como un objeto Java y otras veces como una instancia de 

javax.ws.rs.core.Response
. Cuando se utiliza un objeto Java, se calcula de un objeto Java al tipo de medio establecido en la 
@Produces
anotación. En este servicio en particular, la salida es un documento JSON. Con el 
Response
objeto, tenemos un control detallado de lo que se envía de vuelta a la persona que llama; puede configurar el código de estado HTTP, los encabezados o, por ejemplo, el contenido devuelto a la persona que llama. Depende del caso de uso preferir un enfoque sobre el otro.

Invocación

Una vez que hayamos definido la API para acceder al servicio de libros , es hora de desarrollar el fragmento de código para invocar el servicio de calificación para recuperar la calificación de un libro.

Quarkus utiliza la especificación MicroProfile Rest Client para acceder a servicios externos (HTTP). Proporciona un enfoque de tipo seguro para invocar servicios RESTful a través de HTTP utilizando algunas de las API de JAX-RS 2.0 para lograr coherencia y una reutilización más sencilla.

El primer elemento a crear es una interfaz que representa el servicio remoto usando las anotaciones JAX-RS.


import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/rate")
@RegisterRestClient
public interface RatingService {
 
   @GET
   @Path("/{bookId}")
   @Produces(MediaType.APPLICATION_JSON)
   Rate getRate(@PathParam("bookId") Long bookId);

}

Cuando 

getRate()
se llama al método, se invoca una llamada HTTP remota al 
/rate/{bookId}
reemplazar 
bookId
con el valor establecido en el parámetro del método. Es importante anotar la interfaz con la 
@RegisterRestClient
anotación.

Luego 

RatingService
, es necesario inyectar la interfaz 
BookResource
para ejecutar las llamadas remotas.


import org.eclipse.microprofile.rest.client.inject.RestClient;

@RestClient
RatingService ratingService;

@GET
@Path("/{bookId}")
@Produces(MediaType.APPLICATION_JSON)
public Book book(@PathParam("bookId") Long bookId) {
    final Rate rate = ratingService.getRate(bookId);

    Book book = findBook(bookId);
    return book;
}

La 

@RestClient
anotación inyecta una instancia de proxy de la interfaz, proporcionando la implementación del cliente.

Lo último es configurar la ubicación del servicio (la parte del nombre de host ). En Quarkus, las propiedades de configuración se establecen en un 

src/main/resources/application.properties
archivo. Para configurar la ubicación del servicio, necesitamos usar el nombre completo de la interfaz de Rest Client con URL como clave y la ubicación como valor:


org.acme.RatingService/mp-rest/url=http://localhost:9090

Antes de acceder correctamente al servicio de clasificación sin el 

401 Unauthorized
problema, es necesario abordar el problema de la autenticación mutua.

Autenticación

Los mecanismos de autenticación basados ​​en token permiten que los sistemas autentiquen, autoricen y verifiquen identidades basándose en un token de seguridad. Quarkus se integra con la especificación de seguridad MicroProfile JWT RBAC para proteger los servicios que utilizan tokens de portador JWT.

Para proteger un punto final usando MicroProfile JWT RBAC Security, solo necesitamos anotar el método con una 

@RolesAllowed
anotación.


@GET
@Path("/{bookId}")
@RolesAllowed("Echoer")
@Produces(MediaType.APPLICATION_JSON)
public Book book(@PathParam("bookId") Long bookId)

Luego configuramos el emisor del token y la ubicación de la clave pública para verificar la firma del token en el 

application.properties
archivo:


mp.jwt.verify.publickey.location=https://raw.githubusercontent.com/redhat-developer-demos/quarkus-tutorial/master/jwt-token/quarkus.jwt.pub
mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac

Esta extensión verifica automáticamente que: el token es válido; el emisor es correcto; el token no ha sido modificado; la firma es válida; no ha expirado.

Tanto el servicio de libros y servicio de clasificación están ahora protegidos por el mismo emisor JWT y las teclas, por lo que la comunicación entre los servicios requiere que el usuario sea autenticado proporcionando una portadora válida de ficha en la 

Authentication
cabecera.

Con el servicio de clasificación en funcionamiento, comencemos el servicio de libros con el siguiente comando:


./mvnw compile quarkus:dev

Finalmente, podemos realizar una solicitud para obtener información del libro que proporcione un token web JSON válido como token de portador.

La generación del token está fuera del alcance de este artículo y ya se generó un token:


curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJqZG9lLXVzaW5nLWp3dC1yYmFjIiwiYXVkIjoidXNpbmctand0LXJiYWMiLCJ1cG4iOiJqZG9lQHF1YXJrdXMuaW8iLCJiaXJ0aGRhdGUiOiIyMDAxLTA3LTEzIiwiYXV0aF90aW1lIjoxNTcwMDk0MTcxLCJpc3MiOiJodHRwczpcL1wvcXVhcmt1cy5pb1wvdXNpbmctand0LXJiYWMiLCJyb2xlTWFwcGluZ3MiOnsiZ3JvdXAyIjoiR3JvdXAyTWFwcGVkUm9sZSIsImdyb3VwMSI6Ikdyb3VwMU1hcHBlZFJvbGUifSwiZ3JvdXBzIjpbIkVjaG9lciIsIlRlc3RlciIsIlN1YnNjcmliZXIiLCJncm91cDIiXSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamRvZSIsImV4cCI6MjIwMDgxNDE3MSwiaWF0IjoxNTcwMDk0MTcxLCJqdGkiOiJhLTEyMyJ9.Hzr41h3_uewy-g2B-sonOiBObtcpkgzqmF4bT3cO58v45AIOiegl7HIx7QgEZHRO4PdUtR34x9W23VJY7NJ545ucpCuKnEV1uRlspJyQevfI-mSRg1bHlMmdDt661-V3KmQES8WX2B2uqirykO5fCeCp3womboilzCq4VtxbmM2qgf6ag8rUNnTCLuCgEoulGwTn0F5lCrom-7dJOTryW1KI0qUWHMMwl4TX5cLmqJLgBzJapzc5_yEfgQZ9qXzvsT8zeOWSKKPLm7LFVt2YihkXa80lWcjewwt61rfQkpmqSzAHL0QIs7CsM9GfnoYc0j9po83-P3GJiBMMFmn-vg" localhost:8080/book/1 -v

Y la respuesta es nuevamente un error prohibido:


< HTTP/1.1 401 Unauthorized
< Content-Length: 0

Quizás se pregunte por qué seguimos recibiendo este error después de proporcionar un token válido. Si inspeccionamos la consola del servicio de libros, vemos que se lanzó la siguiente excepción:


org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 401
    at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
    at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)

El motivo de esta excepción es que estamos autenticados y autorizados para acceder al servicio de libros , pero el token de portador no se ha propagado al servicio de clasificación .

Para propagar automáticamente los 

Authorization
encabezados de las solicitudes entrantes a las solicitudes del cliente de descanso, se requieren dos modificaciones.

La primera modificación es modificar la interfaz de Rest Client y anotarla con 

org.eclipse.microprofile.rest.client.inject.RegisterClientHeaders
.


@Path("/rate")
@RegisterRestClient
@RegisterClientHeaders
public interface RatingService {}

La segunda modificación consiste en configurar qué encabezados se propagan entre las solicitudes. Esto se establece en el 

application.properties
archivo:


org.eclipse.microprofile.rest.client.propagateHeaders=Authorization

Ejecute el mismo comando curl que antes y obtendremos el resultado correcto:


< HTTP/1.1 200 OK
< Content-Length: 39
< Content-Type: application/json
<
* Connection #0 to host localhost left intact
{"bookId":2,"name":"Book 2","rating":1}* Closing connection 0

Resiliencia

Tener servicios tolerantes a fallas es importante en una arquitectura de microservicios para evitar propagar una falla de un servicio a todos los llamadores directos e indirectos del mismo. Quarkus integra la especificación MicroProfile Fault Tolerance con las siguientes anotaciones para hacer frente a las fallas:

●     

@Timeout
: define un tiempo de duración máximo para la ejecución antes de que se lance una excepción.
●     
@Retry
: Vuelve a intentar la ejecución si la llamada falla.
●     
@Bulkhead
: Limita la ejecución simultánea para que las fallas en esa área no puedan sobrecargar todo el sistema.
●     
@CircuitBreaker
: Automáticamente a prueba de fallas cuando la ejecución falla repetidamente.
●     
@Fallback
: proporciona una solución alternativa / valor predeterminado cuando falla la ejecución.

Agreguemos tres reintentos con un temporizador de apagado de un segundo entre reintentos en caso de que ocurra un error al acceder al servicio de calificación .


@Retry(maxRetries = 3, delay = 1000)
Rate getRate(@PathParam("bookId") Long bookId);

Ahora detenga el servicio de calificación y ejecute una solicitud. Se lanza la siguiente excepción:


org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: org.apache.http.conn.HttpHostConnectException: Connect to localhost:9090 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused

Obviamente, el error está ahí, pero tenga en cuenta que hubo un tiempo transcurrido de tres segundos antes de que se lance la excepción, ya que se ejecutan tres reintentos con un retraso de un segundo.

En este caso, el servicio de calificación está inactivo, por lo que no hay recuperación posible, pero en un ejemplo del mundo real en el que el servicio de calificación puede estar inactivo por solo un pequeño período de tiempo, o se implementan varias réplicas del servicio, un simple reintento La operación puede ser suficiente para recuperarse y proporcionar una respuesta válida.

Pero, cuando los reintentos no son suficientes cuando se lanza una excepción, podemos propagar el error a las personas que llaman o proporcionar un valor alternativo para la llamada. Esta alternativa puede ser una llamada a otro sistema (es decir, un caché distribuido) o un valor estático.

Para este caso de uso, cuando falla la conexión con el servicio de calificación , se devuelve un valor de calificación de 0.

Para implementar una lógica de respaldo, lo primero que debe hacer es implementar la 

org.eclipse.microprofile.faulttolerance.FallbackHandler
interfaz configurando el tipo de retorno como el mismo tipo que el método de estrategia de respaldo proporciona como valor alternativo. Para este caso, 
Rate
se devuelve un objeto predeterminado .


import org.eclipse.microprofile.faulttolerance.ExecutionContext;
import org.eclipse.microprofile.faulttolerance.FallbackHandler;

public class RatingServiceFallback implements FallbackHandler<Rate> {

   @Override
   public Rate handle(ExecutionContext context) {
       Rate rate = new Rate();
       rate.rate = 0;
       return rate;
   }
 
}

Lo último que debe hacer es anotar el 

getRating()
método con una 
@org.eclipse.microprofile.faulttolerance.Fallback
anotación para configurar la clase de respaldo que se ejecutará cuando no sea posible la recuperación.


@Retry(maxRetries = 3, delay = 1000)
@Fallback(RatingServiceFallback.class)
Rate getRate(@PathParam("bookId") Long bookId);

Si repite la misma solicitud que antes, no se lanza ninguna excepción, pero una salida válida con el campo de calificación se establece en 0.


* Connection #0 to host localhost left intact
{"bookId":2,"name":"Book 2","rating":0}* Closing connection 0

El mismo enfoque se puede utilizar con cualquiera de las otras estrategias proporcionadas por la especificación. Por ejemplo, en el caso de un patrón de interrupción del circuito:


@CircuitBreaker(requestVolumeThreshold = 4,
               failureRatio=0.75,
               delay = 1000)

Si se producen tres (4 x 0,75) fallos entre la ventana móvil de cuatro invocaciones consecutivas, el circuito se abre durante 1000 ms y luego se vuelve a semiabierto. Si la invocación mientras el circuito está medio abierto se realiza correctamente, se vuelve a cerrar. De lo contrario, permanece abierto.

Inicio sesión

En la arquitectura de microservicios, se recomienda recopilar registros de todos los servicios en un registro unificado para un uso y una comprensión más eficientes.

Una solución es utilizar Fluentd , un recopilador de datos de código abierto para una capa de registro unificada en Kubernetes. Quarkus se integra con Fluentd utilizando el formato de registro extendido Graylog (GELF).

La integración es realmente sencilla. Primero, use la lógica de registro como con cualquier otra aplicación de Quarkus:


import org.jboss.logging.Logger;

private static final Logger LOG = Logger.getLogger(BookResource.class);

@GET
@Path("/{bookId}")
@RolesAllowed("Echoer")
@Produces(MediaType.APPLICATION_JSON)
public Book book(@PathParam("bookId") Long bookId) {
    LOG.info("Get Book");

A continuación, habilite el formato GELF y configure la ubicación del servidor Fluentd:


quarkus.log.handler.gelf.enabled=true
quarkus.log.handler.gelf.host=localhost
quarkus.log.handler.gelf.port=12201

Finalmente, podemos hacer una solicitud al punto final registrado:


curl -H "Authorization: Bearer ..." localhost:8080/book/1

{"bookId":1,"name":"Book 1","rating":3}

Nada ha cambiado en términos de producción, pero el logline se ha transmitido a Fluentd. Si se usa Kibana para visualizar datos, veremos la línea de registro almacenada:

Vigilancia

El monitoreo es otro “microservicio” que debe implementarse en nuestra arquitectura de microservicio. Quarkus se integra con Micrometer para el monitoreo de aplicaciones. Micrometer proporciona un único punto de entrada a los sistemas de monitoreo más populares, lo que le permite instrumentar su código de aplicación basado en JVM sin la dependencia del proveedor.

Para este ejemplo, el formato Prometheus se usa como salida de monitoreo, pero Micrometer (y Quarkus) también admite otros formatos como Azure Monitor, Stackdriver, SignalFx, StatsD y DataDog.

Puede registrar la siguiente dependencia de Maven para proporcionar salida de Prometheus:


<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>

La extensión Micrometer registra algunas métricas relacionadas con el sistema, JVM o HTTP de forma predeterminada. Un subconjunto de las métricas recopiladas está disponible en el 

/q/metrics
punto final, como se muestra a continuación:


curl localhost:8080/q/metrics

jvm_threads_states_threads{state="runnable",} 22.0
jvm_threads_states_threads{state="blocked",} 0.0
jvm_threads_states_threads{state="waiting",} 10.0
http_server_bytes_read_count 1.0
http_server_bytes_read_sum 0.0

Pero las métricas específicas de la aplicación también se pueden implementar utilizando la API Micrometer.
Implementemos una métrica personalizada que mida el libro mejor calificado.

El registro de una métrica, en este caso, un indicador, se logra mediante la 

io.micrometer.core.instrument.MeterRegistry
clase.


private final MeterRegistry registry;
private final LongAccumulator highestRating = new LongAccumulator(Long::max, 0);
 
public BookResource(MeterRegistry registry) {
    this.registry = registry;
    registry.gauge("book.rating.max", this,
               BookResource::highestRatingBook);
}

Hagamos algunas solicitudes y validemos que el indicador esté correctamente actualizado.


curl -H "Authorization: Bearer ..." localhost:8080/book/1

{"bookId":1,"name":"Book 1","rating":3}

curl localhost:8080/q/metrics

# HELP book_rating_max
# TYPE book_rating_max gauge
book_rating_max 3.0

También podemos configurar un temporizador para registrar el tiempo dedicado a obtener información de calificación del servicio de calificación.


Supplier<Rate> rateSupplier = () -> {
      return ratingService.getRate(bookId);
};
     
final Rate rate = registry.timer("book.rating.test").wrap(rateSupplier).get();

Hagamos algunas solicitudes y validemos el tiempo dedicado a recopilar calificaciones.


# HELP book_rating_test_seconds
# TYPE book_rating_test_seconds summary
book_rating_test_seconds_count 4.0
book_rating_test_seconds_sum 1.05489108
# HELP book_rating_test_seconds_max
# TYPE book_rating_test_seconds_max gauge
book_rating_test_seconds_max 1.018622001

Micrometer usa 

MeterFilter
instancias para personalizar las métricas emitidas por 
MeterRegistry
instancias. La extensión Micrometer detectará 
MeterFilter
beans CDI y los usará al inicializar 
MeterRegistry
instancias.

Por ejemplo, podemos definir una etiqueta común para configurar el entorno (prod, testing, staging, etc.) donde se ejecuta la aplicación.


@Singleton
public class MicrometerCustomConfiguration {
 
   @Produces
   @Singleton
   public MeterFilter configureAllRegistries() {
       return MeterFilter.commonTags(Arrays.asList(
               Tag.of("env", "prod")));
   }

}

Envíe una nueva solicitud y valide que las métricas ahora estén etiquetadas.


http_client_requests_seconds_max{clientName="localhost",env="prod",method="GET",outcome="SUCCESS",status="200",uri="/rate/2",} 0.0

Tenga en cuenta la etiqueta que 

env
contiene el valor 
prod
.

Rastreo

Las aplicaciones de Quarkus utilizan la especificación OpenTracing para proporcionar un seguimiento distribuido para aplicaciones web interactivas.

Configuremos OpenTracing para conectarse a un servidor Jaeger, estableciendo book-service como el nombre del servicio para identificar los rastros:


quarkus.jaeger.enabled=true
quarkus.jaeger.endpoint=http://localhost:14268/api/traces
quarkus.jaeger.service-name=book-service
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1

Ahora, hagamos una solicitud:


curl -H "Authorization: Bearer ..." localhost:8080/book/1

{"bookId":1,"name":"Book 1","rating":3}

Acceso a la interfaz de usuario de Jaeger para validar que se rastrea la llamada:

FUENTE: https://www.infoq.com/articles/microservicilities-quarkus/?topicPageSponsorship=3109b35a-4fbc-4bfd-94a5-327d255d24e7&itm_source=articles_about_java&itm_medium=link&itm_campaign=java