BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Implémentation De Microservicilités Avec Quarkus Et MicroProfile

Implémentation De Microservicilités Avec Quarkus Et MicroProfile

Points Clés

  • Quarkus est un framework Java natif Kubernetes full-stack conçu pour les machines virtuelles Java (JVM) et la compilation native.
  • Lors du développement d'une architecture de microservices, de nouveaux défis doivent être relevés, tels que l'évolutivité, la sécurité et l'observabilité.
  • Les Microservicilités fournissent une liste de préoccupations transversales pour mettre en œuvre correctement les microservices.
  • Kubernetes est un bon début pour implémenter ces microservicilités, mais il y a quelques lacunes.
  • MicroProfile est une spécification permettant de mettre en œuvre des préoccupations transversales à l'aide du langage de programmation Java.
  • L'authentification parmi les services internes peut être réalisée à l'aide de jetons (par exemple JWT).

Pourquoi des microservicilités ?

Dans une architecture de microservices, une application est formée de plusieurs services interconnectés où tous travaillent ensemble pour produire la fonctionnalité métier requise.

Ainsi, une architecture de microservices d'entreprise typique ressemble à ceci :

Au début, il peut sembler facile d'implémenter une application en utilisant une architecture de microservices.

Mais le faire correctement n'est pas une promenade de santé car il y a de nouveaux défis qui n'étaient pas présents dans une architecture monolithique.

Certains d'entre eux sont la tolérance aux pannes, la découverte de services, la mise à l'échelle, la journalisation et le traçage, pour n'en citer que quelques-uns.

Pour résoudre ces problèmes, chaque microservice doit implémenter ce que nous avons appelé chez Red Hat les «Microservicilités».

Le terme fait référence à une liste de préoccupations transverses qu'un service doit mettre en œuvre en dehors de la logique métier pour résoudre ces problèmes, comme résumé dans le diagramme suivant :

La logique métier peut être implémentée dans n'importe quel langage (Java, Go, JavaScript) ou n'importe quel framework (Spring Boot, Quarkus) mais autour de la logique métier, les préoccupations suivantes doivent être implémentées :

API : le service est accessible via un ensemble défini d'opérations d'une API. Par exemple, dans le cas des API Web RESTful, HTTP est utilisé comme protocole. De plus, l'API peut être documentée à l'aide d'outils tels que Swagger.

Découverte : les services doivent découvrir d'autres services.

Appel : une fois qu'un service est découvert, il doit être appelé avec un ensemble de paramètres et éventuellement renvoyer une réponse.

Élasticité : l'une des caractéristiques importantes d'une architecture de microservices est que chacun des services est élastique, ce qui signifie qu'il peut être mis à l'échelle vers le haut et/ou vers le bas indépendamment en fonction de certains paramètres tels que la criticité du système ou selon la charge de travail actuelle.

Résilience : dans une architecture de microservices, nous devons développer en gardant à l'esprit l'échec, en particulier lors de la communication avec d'autres services. Dans une application monolithique, l'application, dans son ensemble, est vers le haut ou vers le bas. Mais lorsque cette application est décomposée en une architecture de microservices, l'application est composée de plusieurs services et tous sont interconnectés par le réseau, ce qui implique que certaines parties de l'application peuvent être en cours d'exécution tandis que d'autres peuvent échouer. Il est important de contenir l'échec pour éviter de propager l'erreur via les autres services. La résilience (ou résilience d'une application) est la capacité d'une application/d'un service à réagir aux problèmes et à fournir le meilleur résultat possible.

Pipeline : un service doit être déployé indépendamment sans aucune sorte d'orchestration de déploiement. Pour cette raison, chaque service doit avoir son propre pipeline de déploiement.

Authentification : l'un des aspects clés de la sécurité dans une architecture de microservices est de savoir comment authentifier/autoriser les appels entre les services internes. Les jetons Web (et les jetons en général) sont le moyen privilégié pour représenter les demandes en toute sécurité parmi les services internes.

Journalisation : la journalisation est simple dans les applications monolithes, car tous les composants de l'application s'exécutent sur le même nœud. Les composants sont maintenant répartis sur plusieurs nœuds sous forme de services, par conséquent pour avoir une vue complète des traces de journalisation, un système de journalisation/collecteur de données unifié est nécessaire.

Surveillance : mesurer les performances de votre système, comprendre l'état de santé général de l'application et alerter en cas de problème sont des aspects clés pour garantir le bon fonctionnement d'une application basée sur des microservices. La surveillance est un aspect clé pour contrôler l'application.

Traçage : le traçage est utilisé pour visualiser le flux d'un programme et la progression des données. C’est particulièrement utile lorsque, en tant que développeur/opérateur, nous avons besoin de vérifier le parcours de l’utilisateur dans l’ensemble de l’application.

Kubernetes est en train de devenir l'outil de fait pour déployer des microservices. Il s’agit d’un système open source permettant d’automatiser, d’orchestrer, de faire évoluer et de gérer des conteneurs.

Seules trois des dix microservicilités sont couvertes lors de l'utilisation de Kubernetes.

Découverte est mis en œuvre avec le concept d'un service Kubernetes. Il permet de regrouper les pods Kubernetes (agissant comme un seul) avec une adresse IP virtuelle et un nom DNS stables. La découverte d'un service consiste simplement à effectuer des requêtes en utilisant le nom de service de Kubernetes comme nom d'hôte.

L'appel de services est facile avec Kubernetes car la plate-forme elle-même fournit le réseau nécessaire pour appeler l'un des services.

L'élasticité (ou scaling) est quelque chose que Kubernetes avait en tête depuis le tout début, par exemple en exécutant la commande kubectl scale deployment myservice --replicas = 5 , le déploiement de myservice se fait avec cinq réplicas ou instances. La plate-forme Kubernetes s'occupe de trouver les nœuds appropriés, de déployer le service et de maintenir le nombre souhaité de réplicas opérationnels en permanence.

Mais qu'en est-il du reste des microservicilités ? Kubernetes n'en couvre que trois, alors comment pouvons-nous implémenter les autres ?

Il existe de nombreuses stratégies à suivre en fonction du langage ou du framework utilisé, mais dans cet article, nous verrons comment mettre en œuvre certaines d'entre elles en utilisant Quarkus.

Qu'est-ce que Quarkus ?

Quarkus est un framework Java natif Kubernetes full-stack conçu pour les machines virtuelles Java (JVM) et la compilation native, optimisant Java spécifiquement pour les conteneurs, et lui permettant de devenir une plate-forme efficace pour les environnements serverless, cloud et Kubernetes.

Au lieu de réinventer la roue, Quarkus utilise des frameworks d'entreprise bien connus soutenus par des normes/spécifications et les rend compilables en binaire à l'aide de GraalVM.

Qu'est-ce que MicroProfile ?

Quarkus s'intègre à la spécification MicroProfile en déplaçant l'écosystème Java d'entreprise dans l'architecture des microservices.

Dans le schéma suivant, nous voyons toutes les API qui composent la spécification MicroProfile. Certaines API, comme CDI, JSON-P et JAX-RS, sont basées sur la spécification Jakarta EE (anciennement Java EE). Les autres ont été développées par la communauté Java.

Implémentons l'API, l'appel, la résilience, l'authentification, la journalisation, la surveillance et le traçage à l'aide de Quarkus.

Comment mettre en œuvre des microservicilités à l'aide de Quarkus

Pour commencer

Le moyen le plus rapide de commencer à utiliser Quarkus consiste à utiliser la page de démarrage en ajoutant les dépendances requises. Pour cet exemple, les dépendances suivantes sont enregistrées pour répondre aux exigences des microservicilités :

  • API : RESTEasy JAX-RS, RESTEasy JSON-B, OpenAPI
  • Appel : REST Client JSON-B
  • Résilience : Fault Tolerance
  • Authentification : JWT
  • Journalisation : GELF
  • Surveillance : Micrometer metrics
  • Suivi : OpenTracing

Nous pouvons sélectionner manuellement chacune des dépendances ou accéder au lien suivant Microservicilities Quarkus Generator où tous sont sélectionnés. Appuyez ensuite sur le bouton Generate your application pour télécharger le fichier zip contenant l'application créée.

Le service

Pour cet exemple, une application très simple est générée avec seulement deux services. Un service, nommé rating service, renvoie les évaluations d'un livre donné, et un autre service, nommé book service, renvoie les informations d'un livre avec ses évaluations. Tous les appels entre services doivent être authentifiés.

Dans la figure suivante, nous voyons un aperçu du système complet :

Le Rating service est déjà développé et fourni sous forme d'un conteneur Linux. Démarrez le service sur le port 9090 en exécutant la commande suivante :

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

Pour valider le service, faites une requête sur l'url 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

Le code d'état renvoyé est 401 Unauthorized car aucune information d'autorisation n'a été fournie dans la requête sous la forme d'un jeton bearer (JWT). Seuls les jetons valides avec le group Echoer sont autorisés à accéder au rating service.

L'API

Quarkus utilise la spécification JAX-RS bien connue pour définir les API Web RESTful. Sous le capot, Quarkus utilise l'implémentation RESTEasy fonctionnant directement avec le framework Vert.X sans utiliser la technologie Servlet.

Définissons une API pour le service book mettant en œuvre les opérations les plus courantes :

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();
   }
}

La première chose à noter est que quatre endpoints différents sont définis :

  • GET /book/{bookId} utilise la méthode HTTP GET pour renvoyer les informations du livre avec sa notation. L'élément de retour est automatiquement transformé en JSON.
  • POST /book utilise la méthode HTTP POST pour insérer un livre venant en tant que contenu du corps. Le contenu du corps est automatiquement marshalé de JSON vers un objet Java.
  • DELETE /book/{bookId} utilise la méthode HTTP DELETE pour supprimer un livre par son ID.
  • GET /book/search?description={description} recherche les livres en fonction de leur description.

La deuxième chose à noter est le type de retour, parfois comme un objet Java et d'autres fois comme une instance de javax.ws.rs.core.Response. Lorsqu'un objet Java est utilisé, il est marshalé d'un objet Java vers le type de média défini dans l'annotation @Produces. Dans ce service particulier, la sortie est un document JSON. Avec l'objet Response, nous avons un contrôle fin sur ce qui est renvoyé à l'appelant ; vous pouvez définir le code statut HTTP, les en-têtes ou, par exemple, le contenu renvoyé à l'appelant. Cela dépend du cas d'utilisation pour préférer une approche à l'autre.

L'invocation

Une fois que nous avons défini l'API pour accéder au book service, il est temps de développer le morceau de code pour appeler le rating service afin de récupérer la note d'un livre.

Quarkus utilise la spécification MicroProfile Rest Client pour accéder aux services externes (via HTTP). Il fournit une approche type-safe pour appeler des services RESTful via HTTP à l'aide de certaines des API JAX-RS 2.0 pour la cohérence et une réutilisation plus facile.

Le premier élément à créer est une interface représentant le service distant à l'aide des annotations 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);

}

Lorsque la méthode getRate() est appelée, un appel HTTP distant est appelé à /rate/{bookId} en remplaçant bookId par la valeur définie dans le paramètre de méthode. Il est important d'annoter l'interface avec l'annotation @RegisterRestClient.

Ensuite, l'interface RatingService doit être injectée dans BookResource pour exécuter les appels distants.

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;
}

L'annotation @RestClient injecte une instance proxy de l'interface, fournissant l'implémentation du client.

La dernière chose à faire est de configurer l'emplacement du service (la partie hostname). Dans Quarkus, les propriétés de configuration sont définies dans le fichier src/main/resources/application.properties. Pour configurer l'emplacement du service, nous devons utiliser le nom complet de l'interface Rest Client avec l'URL comme clé et l'emplacement comme valeur :

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

Avant d'accéder correctement au service rating sans le problème de 401 Unauthorized, le problème d'authentification mutuelle doit être résolu.

L'authentification

Les mécanismes d'authentification par jeton permettent aux systèmes d'authentifier, d'autoriser et de vérifier les identités en fonction d'un jeton de sécurité. Quarkus s'intègre à la spécification MicroProfile JWT RBAC Security pour protéger les services à l'aide de jetons JWT Bearer.

Pour protéger un endpoint à l'aide de MicroProfile JWT RBAC Security, il suffit d'annoter la méthode avec l'annotation @RolesAllowed.

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

Ensuite, nous configurons l'émetteur du token et l'emplacement de la clé publique pour vérifier la signature du token dans le fichier application.properties :

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

Cette extension vérifie automatiquement que : le jeton est valide ; l'émetteur est correct ; le jeton n'a pas été modifié ; la signature est valide ; il n’a pas expiré.

Le book service et le rating service sont désormais protégés par le même émetteur et les mêmes clés JWT, de sorte que la communication entre les services nécessite que l'utilisateur soit authentifié en fournissant un jeton bearer valide dans le header Authentication.

Une fois le rating service démarré, démarrons le book service avec la commande suivante :

./mvnw compile quarkus:dev

Enfin, nous pouvons faire une demande pour obtenir des informations sur le livre en fournissant un JSON Web Token valide en tant que jeton bearer.

La génération du jeton n'entre pas dans le cadre de cet article et un jeton a déjà été généré :

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

Et la réponse est à nouveau une erreur interdisant l'accès :

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

Vous pourriez vous demander pourquoi nous obtenons toujours cette erreur après avoir fourni un jeton valide. Si nous inspectons la console du service de livre, nous voyons que l'exception suivante a été levée :

.
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)

La raison de cette exception est que nous sommes authentifiés et autorisés à accéder au book service, mais le jeton bearer n'a pas été propagé au rating service.

.

Pour propager automatiquement les en-têtes Authorization des requêtes entrantes vers les requêtes rest-client, deux modifications sont nécessaires.

.

La première modification consiste à modifier l'interface Rest Client et à l'annoter avec org.eclipse.microprofile.rest.client.inject.RegisterClientHeaders.

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

La deuxième modification consiste à configurer les en-têtes qui sont propagées dans les requêtes. Ceci est défini dans le fichier application.properties:

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

Exécutez la même commande curl que précédemment et nous obtiendrons la sortie correcte :

< 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

La résilience

Disposer de services tolérants aux pannes est important dans une architecture de microservices pour éviter de propager une panne d'un service à tous les appelants directs et indirects de celui-ci. Quarkus intègre la spécification MicroProfile Fault Tolerance avec les annotations suivantes pour gérer les échecs :

●     @Timeout : définit une durée maximale d'exécution avant qu'une exception ne soit levée.
●     @Retry : relance l'exécution si l'appel échoue.
●     @Bulkhead : limite l'exécution simultanée afin que les échecs dans cette zone ne puissent pas surcharger l'ensemble du système.
●     @CircuitBreaker : échoue automatiquement rapidement lorsque l'exécution échoue à plusieurs reprises.
●     @Fallback : fournit une solution alternative/valeur par défaut lorsque l'exécution échoue.

Ajoutons trois tentatives avec un minuteur d'attente d'une seconde entre les tentatives au cas où une erreur se produirait lors de l'accès au rating service.

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

Arrêtez maintenant le rating service et exécutez une requête. L'exception suivante est levée :

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

De toute évidence, l'erreur est là, mais notez qu'il y a eu un temps écoulé de trois secondes avant que l'exception soit levée car trois tentatives avec un délai d'une seconde sont exécutées.

Dans ce cas, le rating service est en panne, il n'y a donc pas de récupération possible, mais dans un exemple concret où le rating service peut être en panne pour une durée courte, ou plusieurs réplicas du service sont déployées, une simple opération de relance peut suffire pour récupérer et fournir une réponse valide.

Mais, lorsque les tentatives ne sont pas suffisantes lorsqu'une exception est levée, nous pouvons soit propager l'erreur aux appelants, soit fournir une valeur alternative pour l'appel. Cette alternative peut être un appel à un autre système (par exemple à un cache distribué) ou une valeur statique.

Dans ce cas d'utilisation, lorsque la connexion au rating service échoue, une valeur 0 pour la notation est renvoyée.

Pour implémenter une logique de repli (fallback), la première chose à faire est d'implémenter l'interface org.eclipse.microprofile.faulttolerance.FallbackHandler en définissant le type de retour comme le même type que la méthode de la stratégie de repli fournit comme valeur alternative. Dans ce cas, un objet Rate par défaut est renvoyé.

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;
   }
 
}

La dernière chose à faire est d'annoter la méthode getRating() avec l'annotation @org.eclipse.microprofile.faulttolerance.Fallback pour configurer la classe de repli à exécuter lorsqu'aucune récupération n'est possible.

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

Si vous répétez la même requête que précédemment, aucune exception n'est levée mais une sortie valide avec le champ de notation est mis à 0.

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

La même approche peut être utilisée avec n'importe laquelle des autres stratégies fournies par la spécification. Par exemple, dans le cas du motif circuit breaking :

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

Si trois (4 x 0,75) échecs se produisent parmi la fenêtre glissante de quatre invocations consécutives, alors le circuit est ouvert pendant 1000 ms, puis revient à demi-ouvert. Si l'invocation alors que le circuit est semi-ouvert réussit, alors il est refermé. Sinon, il reste ouvert.

La journalisation

Dans l’architecture des microservices, il est recommandé de collecter les journaux de tous les services dans un seul journal unifié pour une utilisation et une compréhension plus efficaces.

Une solution consiste à utiliser Fluentd, un collecteur de données open source pour une couche de journalisation unifiée dans Kubernetes. Quarkus s'intègre à Fluentd en utilisant le format Graylog Extended Log Format (GELF).

L'intégration est vraiment simple. Tout d'abord, utilisez la logique de journalisation comme avec toute autre application 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");

Ensuite, activer le format GELF et définir l'emplacement du serveur Fluentd :

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

Finalement, nous pouvons faire une requête vers le endpoint enregistré :

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

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

Rien n'a changé en termes de sortie, mais la logline a été transmise à Fluentd. Si Kibana est utilisé pour visualiser les données, nous verrons la ligne de journal stockée :

.

Le monitoring

La surveillance est une autre "microservicilité" qui doit être implémentée dans notre architecture de microservices. Quarkus s'intègre à Micrometer pour la surveillance des applications. Micrometer fournit un point d'entrée unique vers les systèmes de surveillance les plus populaires, ce qui vous permet d'instrumenter votre code d'application basé sur la JVM sans verrouiller le fournisseur.

.

Pour cet exemple, le format Prometheus est utilisé comme sortie de surveillance, mais Micrometer (et Quarkus) prend également en charge d'autres formats comme Azure Monitor, Stackdriver, SignalFx, StatsD et DataDog.

Vous pouvez enregistrer la dépendance Maven suivante pour fournir une sortie Prometheus :

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

L'extension Micrometer enregistre par défaut certaines métriques liées au système, à la JVM ou à HTTP. Un sous-ensemble des métriques collectées est disponible au endpoint /q/metrics, comme indiqué ci-dessous :

.
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

Mais des métriques spécifiques à une application peuvent également être mises en œuvre à l'aide de l'API Micrometer.
Mettons en œuvre une métrique personnalisée qui mesure le livre le mieux noté.

L'enregistrement d'une métrique, dans ce cas, une jauge, est accompli en utilisant la classe io.micrometer.core.instrument.MeterRegistry.

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);
}

Faisons quelques requêtes et validons que la jauge est correctement mise à jour.

.
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

Nous pouvons également définir un timer pour enregistrer le temps passé à obtenir des informations de notation auprès du rating service.

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

Faisons quelques requêtes et validons le temps passé à recueillir des évaluations.

# 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 utilise des instances de MeterFilter pour personnaliser les métriques émises par les instances de MeterRegistry. L'extension Micrometer détectera les beans CDI MeterFilter et les utilisera lors de l'initialisation des instances MeterRegistry.

Par exemple, nous pouvons définir une balise commune pour définir l'environnement (prod, testing, staging, etc.) dans lequel l'application est exécutée.

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

}

Envoyez une nouvelle requête et validez que les métriques sont maintenant balisées.

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

Notez le tag env contenant la valeur prod.

Le traçage

Les applications Quarkus utilisent la spécification OpenTracing pour fournir un traçage distribué pour les applications web interactives.

Configurons OpenTracing pour qu'il se connecte à un serveur Jaeger, en définissant book-service comme nom de service pour identifier les traces :

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

Maintenant, faisons une requête :

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

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

Accéder à l'interface utilisateur de Jaeger pour valider que l'appel est tracé:

Conclusions

Développer et mettre en œuvre une architecture de microservices est un peu plus difficile que de développer une application monolithique. Nous pensons que les microservices peuvent vous pousser à développer des services correctement en termes d'infrastructure d'application.

La plupart des microservices expliqués ici (sauf API et Pipelines) sont nouveaux ou sont mis en œuvre différemment dans les applications monolithiques. La raison en est que maintenant, l'application est décomposée en plusieurs morceaux, tous interconnectés au sein du réseau.

Si vous envisagez de développer des microservices et de les déployer sur Kubernetes, alors Quarkus est une bonne solution car il s'intègre en douceur à Kubernetes. L'implémentation de la plupart des microservices est simple et ne nécessite que quelques lignes de code.

Le code source démontré dans cet article peut être trouvé sur github.

A propos de l'auteur

Alex Soto est Director of Developer Experience chez Red Hat. Il est passionné par le monde Java, l'automatisation des logiciels, et il croit au modèle du logiciel libre. Alex Soto est le co-auteur de Manning | Testing Java Microservices et O'Reilly | Quarkus Cookbook et contribue à plusieurs projets open-source. Java Champion depuis 2017, il est également conférencier international et enseignant à l'université Salle URL. Vous pouvez le suivre sur Twitter (Alex Soto ⚛️) pour rester au courant de ce qui se passe dans le monde de Kubernetes et de Java.

 

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT