Tutoriel pour apprendre à créer une API REST, avec Java et Vert.x, en 5 minutes

Vert.x est une API asynchrone très proche du modèle d'acteurs. Vert.x est polyglotte, simple, scalable (élastique) et hautement concurrente. Vert.x est bien adapté aux architectures en microservices.

Dans cet article rapide, nous allons voir comment créer une API standard avec Vert.x. Et pour cela, on se donne 5 minutes… Commentez Donner une note  l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Créer une API en Java est un sujet déjà largement traité. Si vous utilisez Jersey et ses annotations, vous savez que c’est très simple et rapide à mettre en place. Dans cet article, je voudrais simplement vous montrer ce qu’on peut faire avec Vert.x en revenant aux fondamentaux. Et en vérité, la création d’une API sera avant tout un prétexte.

II. Domaine

Si vous lisez régulièrement mes articles, vous savez qu’ils parlent tous de chiens. C’est un domaine simple qui ne nécessite pas de longues explications. Le bean Dog suivant sera suffisant pour cette chronique.

Dog.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public class Dog {
  private final String id;
  private final String name;
  private final String race;
  private final int age;
  // Constructeur
  public Dog(final String id, final String name, final String race, final int age) {
    super();
    this.id = id;
    this.name = name;
    this.race = race;
    this.age = age;
  }
  //
  // + getters

Je vous propose également DogService qui fera office de service, avec les opérations classiques. Un code complet naïf est donné en annexes.

DogService.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
public class DogService {
  // Charcher la liste des chiens
  public List<Dog> findAll() {
    ...
  }
  // Chercher un seul chien
  public Dog findById(final String id) {
    ...
  }
  // Mettre à jour un chien
  public Dog update(final Dog dog) {
    ...
  }
  // Effacer un chien
  public void remove(final String id) {
    ...
  }
  // Ajouter un chien
  public Dog add(final Dog dog) {
    ...
  }

Et finalement, je vous propose d’initialiser une liste d’exemples composée des chiens célèbres.

DogService.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
public class DogService {
  private final Map<String, Dog> dogs = new HashMap<String, Dog>();
  // Constructeur
  public DogService() {
    super();
    initDogs();
  }
  // Initialisation des chiens fictifs
  private void initDogs() {
    // Création de chiens fictifs
    final Dog lassie = new Dog("12345fh", "Lassie", "colley", 12);
    final Dog milou = new Dog("e7654", "Milou", "fox-terrier", 11);
    final Dog scoobydoo = new Dog("s93d78", "Scooby-Doo", "Danois", 7);
    final Dog idefix = new Dog("6222mk9p", "Idefix", "Bichon", 17);
    // Ajouts dans la map
    dogs.put(lassie.getId(), lassie);
    dogs.put(milou.getId(), milou);
    dogs.put(scoobydoo.getId(), scoobydoo);
    dogs.put(idefix.getId(), idefix);
  }

III. Créer un serveur

III-A. Installation

Comme souvent, le plus simple va être de passer par Maven, Gradle, etc. Dans cet article, nous allons utiliser Maven, mais sentez-vous libre d’utiliser votre système préféré.

Nous allons donc ajouter une dépendance à Vert.x dans le fichier pom.xml. Vert.x est modulaire. Nous pouvons donc indiquer précisément les dépendances qui nous intéressent, à commencer par vertx-core. Et puisque nous allons développer pour le web, nous ajouterons également vertx-web, le module dédié.

pom.xml
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-core</artifactId>
  <version>${vertx.version}</version>
</dependency>
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web</artifactId>
  <version>${vertx.version}</version>
</dependency>

III-B. Le verticle en charge de l'API

Contrairement à ce que vous verrez dans d’autres tutoriels, je préfère séparer mes verticles de mon main. Je vous encourage à faire de même dans vos applications. On va donc créer MyApiVerticle. Pour cela, il suffit d’hériter de AbstractVerticle et de réécrire les methodes start() et stop(). Demandez à votre IDE de le faire pour vous.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
import io.vertx.core.AbstractVerticle;
public class MyApiVerticle extends AbstractVerticle {
  // Quand le verticle se lance
  @Override
  public void start() throws Exception {
  }
  // Quand le verticle s'arrête
  @Override
  public void stop() throws Exception {
  }

On va en profiter pour ajouter des logs. Vert.x fournit ses propres classes, mais rien ne vous empêche d’utiliser une autre bibliothèque, comme Flogger dont je parlais dans un précédent article, sobrement intitulé « Tutoriel pour logguer facilement, en Java à l'aide de Flogger, en 5 minutes ».

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
public class MyApiVerticle extends AbstractVerticle {
  private static final Logger LOGGER = LoggerFactory.getLogger(MyApiVerticle.class);
  // Quand le verticle se lance
  @Override
  public void start() throws Exception {
    LOGGER.info("Dans le start...");
  }
  // Quand le verticle s'arrête
  @Override
  public void stop() throws Exception {
    LOGGER.info("Dans le stop...");
  }

Il ne reste plus qu’à lancer le verticle depuis le main, de la même façon que dans mes autres articles dédiés à Vert.x.

App.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
import io.vertx.core.Vertx;
public class App {
  public static void main(String[] args) {
    System.out.println("App...");
    final Vertx vertx = Vertx.vertx();
    vertx.deployVerticle(new MyApiVerticle());
  }

Il faut ensuite exécuter le programme et regarder les logs dans la console.

Console
Sélectionnez
1.
2.
3.
App...
sept. 1, 2019 10:10:49 AM com.ice.articlevertxapi.MyApiVerticle
INFOS: Dans le start...

III-C. Ecouter un port

On y est presque. Avant d’entrer dans le vif du sujet, je vous propose d’écrire un simple Hello World accessible depuis le navigateur. Il faut simplement créer un serveur, depuis la variable vertx fournie par AbstractVerticle. Dans le cas du Hello World, la lambda fournie au RequestHandler est très simple. Enfin, je vous propose d’écouter sur le port 8080.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
@Override
public void start() throws Exception {
  LOGGER.info("Dans le start...");
  vertx.createHttpServer()
      .requestHandler(routingContext -> routingContext.response().end("Hello World!"))
      .listen(8080);
}

Il ne reste plus à rechercher l'adresse http://localhost:8080/ dans votre navigateur.

Image non disponible

IV. Notre API naïve

IV-A. Charger une liste d'objets

On va maintenant écrire notre API. Dans un premier temps, commençons par l’opération qui me semble la plus simple : charger la liste avec tous les chiens.

Pour cela, je vous propose de définir la route « /api/v1/dogs » qui est composée de « api » pour préciser qu’on est sur l’URL de notre API, de « v1 » pour dire qu’on est en version 1, et de « dogs » au pluriel pour indiquer qu’on veut la liste des chiens.

Le code associé est relativement simple.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
@Override
public void start() throws Exception {
  LOGGER.info("Dans le start...");
  // Création du routeur
  Router router = Router.router(vertx);
  // Définition de la route
  router.get("/api/v1/dogs")
      .handler(this::getAllDogs);
  // Lancement du serveur
  vertx.createHttpServer()
      .requestHandler(router)
      .listen(8080);

Note : les tutoriels un peu plus vieux proposaient de faire appel à router::accept comme RequestHandler. Mais la méthode accept est désormais dépréciée. Dans les nouvelles versions, on peut passer directement le router, ce qui est plus simple.

A ce stade, le plus simple est de définir le code propre au traitement de la liste dans la méthode séparée getAllDogs, pour que ça reste lisible.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
private void getAllDogs(RoutingContext routingContext) {
  LOGGER.info("Dans getAllDogs...");
}

J’aimerais attirer votre attention sur le fait que la méthode getAllDogs renvoie void et non la liste de chiens. Cela peut vous déstabiliser en première approche, mais n’oubliez pas qu’on développe une API REST asynchrone. C’est Vert.x qui se chargera d’envoyer la réponse au client.

On peut relancer le programme se demander l’URL « http://localhost:8080/api/v1/dogs » depuis le navigateur. Celui-ci va tourner en boucle, puisqu’on n’a pas encore programmé l’envoi de la liste de chiens, mais on peut vérifier que la log s’affiche dans la console, signe que le routage a bien fonctionné.

Console
Sélectionnez
App...
sept. 1, 2019 10:10:19 AM com.ice.articlevertxapi.MyApiVerticle
INFOS: Dans le start...
sept. 1, 2019 10:10:33 AM com.ice.articlevertxapi.MyApiVerticle
INFOS: Dans getAllDogs...

Avant d’aller plus loin, je vous propose de compléter le code afin d’avoir naïvement accès au service de chiens présenté plus haut. Ici, je vais simplement utiliser l'instruction new, mais il est bien entendu possible de faire de l'injection. Pensez juste à choisir une bibliothèque rapide.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
public class MyApiVerticle extends AbstractVerticle {
  private final DogService dogService = new DogService();
  ...
  private void getAllDogs(RoutingContext routingContext) {
    LOGGER.info("Dans getAllDogs...");
    final List<Dog> dogs = dogService.findAll();
  }

On souhaite répondre au format JSON. On va donc créer l’objet correspondant et on y placera la liste de chiens. Et bien entendu, on peut ajouter autant d’attributs qu’on le souhaite.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
import io.vertx.core.json.JsonObject;
...
private void getAllDogs(RoutingContext routingContext) {
  LOGGER.info("Dans getAllDogs...");
  // Recherche des chiens
  final List<Dog> dogs = dogService.findAll();
  // Création et remplissage de la réponse
  final JsonObject jsonResponse = new JsonObject();
  jsonResponse.put("dogs", dogs);
  jsonResponse.put("my-name", "Thierry");
}

Et finalement, on peut demander à Vert.x d’envoyer la réponse. Ici, je précise le statut 200 pour indiquer que tout s’est bien déroulé. En cas d’erreur, j’aurais indiqué un statut 500 par exemple. J’ajoute aussi un entête (header) pour préciser le content-type et indiquer qu’on renvoie du JSON. Et bien entendu, j’envoie la réponse.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
import io.vertx.core.json.Json;
...
private void getAllDogs(RoutingContext routingContext) {
  LOGGER.info("Dans getAllDogs...");
  // Recherche des chiens
  final List<Dog> dogs = dogService.findAll();
  // Création et remplissage de la réponse
  final JsonObject jsonResponse = new JsonObject();
  jsonResponse.put("dogs", dogs);
  jsonResponse.put("my-name", "Thierry");
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(200)
      .putHeader("content-type", "application/json")
      .end(Json.encodePrettily(jsonResponse));
}

Vous pouvez rafraîchir votre navigateur sur l’adresse « http://localhost:8080/api/v1/dogs » et constater que vous recevez bien la liste des chiens au format JSON.

Image non disponible

Note : Dans l’exemple, l’utilisation de encodePrettily est facultative. Je l’utilise uniquement pour que la réponse soit facile à lire. La plupart des clients proposent également cette facilité.

Pour la suite, je vous propose d’utiliser le logiciel Postman à la place du navigateur. Cela nous donnera plus de possibilités, en particulier pour les autres opérations CRUD.

Lancez donc une requête GET sur l'adresse « http://localhost:8080/api/v1/dogs » depuis Postman.

Image non disponible

Vous constatez que la barre d'onglets vous permet de formater la réponse pour qu'elle soit lisible. Il n'est donc plus nécessaire de faire appel à encodePrettily.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
private void getAllDogs(RoutingContext routingContext) {
  LOGGER.info("Dans getAllDogs...");
  // Recherche des chiens
  final List<Dog> dogs = dogService.findAll();
  // Création et remplissage de la réponse
  final JsonObject jsonResponse = new JsonObject();
  jsonResponse.put("dogs", dogs);
  jsonResponse.put("my-name", "Thierry");
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(200)
      .putHeader("content-type", "application/json")
      .end(Json.encode(jsonResponse));
}

L'affichage en RAW n'est pas très lisible.

Image non disponible

Contrairement à l'affichage « pretty ».

Image non disponible

Mais je préfère que ce soit le client qui réalise la mise en forme, s'il en a vraiment besoin. Ce sera toujours quelques nanosecondes de gagnées sur le serveur et durant le transfert.

IV-B. Charger un seul objet

L’opération CRUD qu’on va naturellement développer ensuite est celle qui consiste à charger un chien, en particulier grâce à son id. Disons qu’on veuille charger les informations de Milou. Dans ce cas, il faudra appeler l’URL « http://localhost:8080/api/v1/dogs/e7654 » dans laquelle dogs est toujours écrit au pluriel, et où « e7654 » est l'id de Milou.

La syntaxe pour préciser un paramètre dans le routage de l’URL est :param

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
@Override
public void start() throws Exception {
  LOGGER.info("Dans le start...");
  // Création du routeur
  Router router = Router.router(vertx);
  // Définition des routes
  router.get("/api/v1/dogs").handler(this::getAllDogs);
  router.get("/api/v1/dogs/:id").handler(this::getOneDog);

Il faudra bien entendu récupérer cet id depuis la requête dans la méthode.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
private void getOneDog(RoutingContext routingContext) {
  LOGGER.info("Dans getOneDog...");
  final String id = routingContext.request().getParam("id");

La suite est relativement simple. Il faut récupérer le chien à l’aide du service et l’envoyer au client.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
private void getOneDog(RoutingContext routingContext) {
  LOGGER.info("Dans getOneDog...");
  // Param id de la requête
  final String id = routingContext.request().getParam("id");
  // Recherche du chien correspondant à l'id
  final Dog dog = dogService.findById(id);
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(200)
      .putHeader("content-type", "application/json")
      .end(Json.encode(dog));
}

Note : Ici je n’utilise pas d’objet JSON intermédiaire, car je souhaite renvoyer uniquement le chien, sans information complémentaire.

Il ne reste plus qu'à tester l'adresse http://localhost:8080/api/v1/dogs/e7654 dans Postman.

Image non disponible

IV-C. Envoyer une erreur

La gestion d’erreur devrait monopoliser une grande partie de votre temps. Dans ce tutoriel, on va se concentrer sur le cas simple, correspondant aux identifiants non trouvés, mais on pourrait parler de nombreux types d’erreurs : sécurité, restrictions, problème de connexion aux ressources (comme la base de données), bugs, etc.

Dans l'exemple, on pourrait renvoyer une erreur si le chien n'est pas trouvé.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
private void getOneDog(RoutingContext routingContext) {
  LOGGER.info("Dans getOneDog...");
  // Param id de la requête
  final String id = routingContext.request().getParam("id");
  // Recherche du chien correspondant à l'id
  final Dog dog = dogService.findById(id);
  // Gestion d'erreur si le chien n'est pas trouvé...
  if (dog == null) {
    // TODO chien non trouvé...
  }
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(200)
      .putHeader("content-type", "application/json")
      .end(Json.encode(dog));
}

Il suffit de créer un message JSON contenant les informations que vous jugerez utiles et de préciser un statut 404 indiquant que la ressource n’a pas été trouvée.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
if (dog == null) {
  final JsonObject errorJsonResponse = new JsonObject();
  errorJsonResponse.put("error", "No dog can be found for the specified id:" + id);
  errorJsonResponse.put("id", id);
  // Envoi de la réponse avec erreur 404
  routingContext.response()
      .setStatusCode(404)
      .putHeader("content-type", "application/json")
      .end(Json.encode(errorJsonResponse));
  return;
}

Pour pouvez tester que vous recevez bien un message d’erreur quand vous appelez l’adresse http://localhost:8080/api/v1/dogs/abc123 depuis Postman, sachant qu’aucun chien n’a l’id « abc123 ». Vous pouvez vérifier (sur la droite dans l'interface de Postman) que le statut est bien 404 Not Found.

Image non disponible

IV-D. Créer un objet

Habituellement, on utilise le verbe POST pour créer un nouvel objet, ce qui est très simple à configurer dans le router.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
@Override
public void start() throws Exception {
  ...
  // Définition des routes
  router.get("/api/v1/dogs").handler(this::getAllDogs);
  router.get("/api/v1/dogs/:id").handler(this::getOneDog);
  router.post("/api/v1/dogs").handler(this::createOneDog);

Il faut ensuite récupérer les informations transmises dans la requête. Pour charger un objet par son id en GET, on avait récupéré la valeur de l’id dans l’URL. En POST, on trouvera les informations dans le corps (body). Je peux ensuite créer un nouveau chien à partir de ces informations et demander au service de l'ajouter à la liste.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
private void createOneDog(RoutingContext routingContext) {
  LOGGER.info("Dans createOneDog...");
  final JsonObject body = routingContext.getBodyAsJson();
  final String name = body.getString("name");
  final String race = body.getString("race");
  final Integer age = body.getInteger("age");

Pour pouvoir récupérer le corps (body), on doit spécifier un BodyHandler sur les routes "/api/v1/dogs*", où l'étoile sert de joker, car il n'y en a pas par défaut.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
import io.vertx.ext.web.handler.BodyHandler;
...
public class MyApiVerticle extends AbstractVerticle {
  ...
  @Override
  public void start() throws Exception {
    LOGGER.info("Dans le start...");
    // Création du routeur
    Router router = Router.router(vertx);
    // Body handler
    router.route("/api/v1/dogs*").handler(BodyHandler.create());
    // Définition des routes
    router.get("/api/v1/dogs").handler(this::getAllDogs);
    router.get("/api/v1/dogs/:id").handler(this::getOneDog);
    router.post("/api/v1/dogs").handler(this::createOneDog);

Il est temps d'ajouter un nouveau chien dans la liste à partir des informations reçues. Je crée donc d'abord (immutable) un chien sans id. C'est le service qui aura la charge de le créer. En retour, le service me renvoie le chien créé, avec maintenant un id.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
private void createOneDog(RoutingContext routingContext) {
  ...
  final Dog dog = new Dog(null, name, race, age);
  final Dog createdDog = dogService.add(dog);

Il ne reste plus qu'à notifier Vert.x que la réponse peut être envoyée. Elle contient le nouveau chien. Et bien entendu, puisqu'il s'agit d'une création, on indique le statut 201.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
private void createOneDog(RoutingContext routingContext) {
  ...
  // TODO Vérification des champs...
  ...
  // Création d'un chien
  final Dog dog = new Dog(null, name, race, age);
  final Dog createdDog = dogService.add(dog);
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(201)
      .putHeader("content-type", "application/json")
      .end(Json.encode(createdDog));
}

Dans Postman, on peut maintenant appeler l'URL http://localhost:8080/api/v1/dogs en POST en précisant un body en JSON. On peut vérifier que le statut de retour est bien 201 created et qu'un id a été choisi pour notre nouveau chien.

Image non disponible

Quand on redemande la liste des chiens, on voit que celui que nous venons d'ajouter est bien présent.

Image non disponible

IV-E. Modifier un objet

La mise à jour d’un chien va maintenant être relativement simple, car on a déjà tous les éléments. On va donc aller plus vite. Habituellement, on utilise le verbe PUT et on précise l’id dans l’URL.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
@Override
public void start() throws Exception {
  ...
  // Définition des routes
  router.get("/api/v1/dogs").handler(this::getAllDogs);
  router.get("/api/v1/dogs/:id").handler(this::getOneDog);
  router.post("/api/v1/dogs").handler(this::createOneDog);
  router.put("/api/v1/dogs/:id").handler(this::updateOneDog);

On prend donc l'id dans l'URL et les champs dans le body.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
private void updateOneDog(RoutingContext routingContext) {
  LOGGER.info("Dans updateOneDog...");
  // Param id de la requête
  final String id = routingContext.request().getParam("id");
  // Params pris dans le body
  final JsonObject body = routingContext.getBodyAsJson();
  final String name = body.getString("name");
  final String race = body.getString("race");
  final Integer age = body.getInteger("age");

Il ne reste plus qu'à faire appel au service et renvoyer l'objet mis à jour.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
private void updateOneDog(RoutingContext routingContext) {
  ...
  // Création/maj d'un chien
  final Dog dog = new Dog(id, name, race, age);
  final Dog updatedDog = dogService.update(dog);
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(200)
      .putHeader("content-type", "application/json")
      .end(Json.encode(updatedDog));

Dans Postman, il faudra simplement choisir le verbe PUT sur l'URL http://localhost:8080/api/v1/dogs/s93d78, où « s93d78 » est l'id de Scooby-Doo.

Image non disponible

On peut vérifier que Scooby-Doo a bien été changé.

Image non disponible

IV-F. Supprimer un objet

On arrive à la dernière opération CRUD, consistant à supprimer un chien. À ce stade, cela devrait être une formalité. On commence en indiquant une route sur le verbe DELETE.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
@Override
public void start() throws Exception {
  ...
  // Définition des routes
  router.get("/api/v1/dogs").handler(this::getAllDogs);
  router.get("/api/v1/dogs/:id").handler(this::getOneDog);
  router.post("/api/v1/dogs").handler(this::createOneDog);
  router.put("/api/v1/dogs/:id").handler(this::updateOneDog);
  router.delete("/api/v1/dogs/:id").handler(this::deleteOneDog);

Le contenu de la méthode est ensuite très simple.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
private void deleteOneDog(RoutingContext routingContext) {
  LOGGER.info("Dans deleteOneDog...");
  // Param id de la requête
  final String id = routingContext.request().getParam("id");
  // Suppression du chien
  dogService.remove(id);
  // Envoi de la réponse
  routingContext.response()
      .setStatusCode(200)
      .putHeader("content-type", "application/json")
      .end();
}

Dans Postman, on va supprimer Scooby-Doo en appelant http://localhost:8080/api/v1/dogs/s93d78 sur le verbe DELETE, où « s93d78 » est l'id de Scooby-Doo.

Image non disponible

Et bien entendu, on vérifie que Scooby-Doo a bien été retiré de la liste.

Image non disponible

V. Réorganiser le routeur

Maintenant qu’on a fait l’essentiel de l’API pour nos chiens, on constate que la partie routage commence à être en pagaille. Il faut imaginer que vous aurez probablement plusieurs domaines complexes dans votre programme. Je vous propose donc d’organiser le code par ressource.

On commence par créer la classe DogResource et on y déplace tout le code relatif à l’API chien.

DogResource.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
public class DogResource {
  ...
  private void getAllDogs(final RoutingContext routingContext) {
    ...
  }
  private void getOneDog(final RoutingContext routingContext) {
    ...
  }
  private void createOneDog(final RoutingContext routingContext) {
    ...
  }
  private void createOneDog(final RoutingContext routingContext) {
    ...
  }
  private void deleteOneDog(final RoutingContext routingContext) {
    ...
  }

Je voudrais également que toute la logique de routage spécifique aux chiens soit déportée vers cette classe. Et je voudrais que le routage se fasse plus particulièrement sur la fin de "/api/v1/dogs/".

DogResource.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
public Router getSubRouter(final Vertx vertx) {
  // Création du sous-routeur
  final Router subRouter = Router.router(vertx);
  // Body handler
  subRouter.route("/*").handler(BodyHandler.create());
  // Définition des routes
  subRouter.get("/").handler(this::getAllDogs);
  subRouter.get("/:id").handler(this::getOneDog);
  subRouter.post("/").handler(this::createOneDog);
  subRouter.put("/:id").handler(this::updateOneDog);
  subRouter.delete("/:id").handler(this::deleteOneDog);
  return subRouter;
}

Au niveau du verticle, qui a été nettoyé, je peux définir un SubRouter et mapper toutes les URL en "/api/v1/dogs" dessus.

MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
@Override
public void start() throws Exception {
  LOGGER.info("Dans le start...");
  // Création du routeur
  final Router router = Router.router(vertx);
  // Création de la ressource
  final DogResource dogResource = new DogResource();
  // Définition des routes et sous-routes
  final Router dogSubRouter = dogResource.getSubRouter(vertx);
  router.mountSubRouter("/api/v1/dogs", dogSubRouter);
  // Lancement du serveur
  vertx.createHttpServer()
      .requestHandler(router)
      .listen(8080);
}

Bien entendu, je peux décomposer "/api/v1/dogs" en trois sous routers « imbriqués ». L'étape d'après serait de créer la ressource CatResource et les sous-routeurs associés.

VI. Conclusions

Aurons-nous réussi à écrire une API naïve avec Vert.x en 5 minutes chrono ? On ne doit pas en être loin. On a pu voir que c’est relativement simple.

On a tout de même fait l’impasse sur un ensemble de préoccupations, notamment en ce qui concerne la sécurité.

Pour aller plus loin, je vous recommande le module vertx-web-api-service qui va grandement simplifier la création d’une API standardisée. Ce module vous permet notamment de définir les routes et les contrats en Yaml.

Vos retours et remarques nous aident à améliorer les publications de Developpez.com. N'hésitez donc pas à commenter cet article. Commentez Donner une note  l'article (5)

VII. Remerciements

D'abord, j'adresse mes remerciements à l'équipe Vert.x et aux créateurs de modules complémentaires, pour avoir développé une bibliothèque aussi utile et pour la maintenir. Je n'oublie pas tous les contributeurs qui participent, notamment sur le forum.

Plus spécifiquement en ce qui concerne cet article, je tiens à remercier l'équipe de Developpez.com et plus particulièrement à Mickael Baron et Bruno Barthel.

VIII. Annexes

VIII-A. Liens

Vert.x : https://vertx.io/

Documentation du module vertx-web : https://vertx.io/docs/vertx-web/java/

Documentation du module vertx-web-api-service : https://vertx.io/docs/vertx-web-api-service/java/

Postman : https://www.getpostman.com/

Série dédiée à Vert.x : https://thierry-leriche-dessirier.developpez.com/tutoriels/java/vertx/creer-lancer-tester-verticle/

Autres articles de la série « en 5 minutes » : https://thierry-leriche-dessirier.developpez.com/#5min

VIII-B. Codes complets

pom.xml
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.ice</groupId>
  <artifactId>article-vertx-api</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>article-vertx-api</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <vertx.version>3.8.1</vertx.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-core</artifactId>
      <version>${vertx.version}</version>
    </dependency>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-web</artifactId>
      <version>${vertx.version}</version>
    </dependency>
  </dependencies>
</project>
Dog.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
package com.ice.articlevertxapi.bean;
public class Dog {
  private final String id;
  private final String name;
  private final String race;
  private final int age;
  public Dog(final String id, final String name, final String race, final int age) {
    super();
    this.id = id;
    this.name = name;
    this.race = race;
    this.age = age;
  }
  public String getId() {
    return id;
  }
  public String getName() {
    return name;
  }
  public String getRace() {
    return race;
  }
  public int getAge() {
    return age;
  }
  @Override
  public String toString() {
    return "Dog [id=" + id + ", name=" + name + ", race=" + race + ", age=" + age + "]";
  }
}
DogService.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
package com.ice.articlevertxapi.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.ice.articlevertxapi.bean.Dog;
public class DogService {
  private final Map<String, Dog> dogs = new HashMap<String, Dog>();
  public DogService() {
    super();
    initDogs();
  }
  private void initDogs() {
    final Dog lassie = new Dog("12345fh", "Lassie", "colley", 12);
    final Dog milou = new Dog("e7654", "Milou", "fox-terrier", 11);
    final Dog scoobydoo = new Dog("s93d78", "Scooby-Doo", "Danois", 7);
    final Dog idefix = new Dog("6222mk9p", "Idefix", "Bichon", 17);
    dogs.put(lassie.getId(), lassie);
    dogs.put(milou.getId(), milou);
    dogs.put(scoobydoo.getId(), scoobydoo);
    dogs.put(idefix.getId(), idefix);
  }
  public List<Dog> findAll() {
    return dogs.values().stream()
        .collect(Collectors.toList());
  }
  public Dog findById(final String id) {
    return dogs.get(id);
  }
  public Dog update(final Dog dog) {
    dogs.put(dog.getId(), dog);
    return dog;
  }
  public void remove(final String id) {
    dogs.remove(id);
  }
  public Dog add(final Dog dog) {
    final String id = "fr" + System.currentTimeMillis() + "d";
    final Dog newdog = new Dog(id,
        dog.getName(),
        dog.getRace(),
        dog.getAge());
    dogs.put(id, newdog);
    return newdog;
  }
}
MyApiVerticle.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
package com.ice.articlevertxapi;
import com.ice.articlevertxapi.resource.DogResource;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.Router;
public class MyApiVerticle extends AbstractVerticle {
  private static final Logger LOGGER = LoggerFactory.getLogger(MyApiVerticle.class);
  @Override
  public void start() throws Exception {
    LOGGER.info("Dans le start...");
    final Router router = Router.router(vertx);
    final DogResource dogResource = new DogResource();
    final Router dogSubRouter = dogResource.getSubRouter(vertx);
    router.mountSubRouter("/api/v1/dogs", dogSubRouter);
    vertx.createHttpServer()
        .requestHandler(router)
        .listen(8080);
  }
  @Override
  public void stop() throws Exception {
    LOGGER.info("Dans le stop...");
  }
}
DogResource.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
package com.ice.articlevertxapi.resource;

import java.util.List;

import com.ice.articlevertxapi.bean.Dog;
import com.ice.articlevertxapi.service.DogService;

import io.vertx.core.Vertx;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;

public class DogResource {

  private static final Logger LOGGER = LoggerFactory.getLogger(DogResource.class);

  private final DogService dogService = new DogService();

  public Router getSubRouter(final Vertx vertx) {
    final Router subRouter = Router.router(vertx);

    // Body handler
    subRouter.route("/*").handler(BodyHandler.create());

    // Routes
    subRouter.get("/").handler(this::getAllDogs);
    subRouter.get("/:id").handler(this::getOneDog);
    subRouter.post("/").handler(this::createOneDog);
    subRouter.put("/:id").handler(this::updateOneDog);
    subRouter.delete("/:id").handler(this::deleteOneDog);

    return subRouter;
  }

  private void getAllDogs(final RoutingContext routingContext) {
    LOGGER.info("Dans getAllDogs...");

    final List<Dog> dogs = dogService.findAll();

    final JsonObject jsonResponse = new JsonObject();
    jsonResponse.put("dogs", dogs);
    jsonResponse.put("my-name", "Thierry");

    routingContext.response()
        .setStatusCode(200)
        .putHeader("content-type", "application/json")
        .end(Json.encode(jsonResponse));
  }

  private void getOneDog(final RoutingContext routingContext) {
    LOGGER.info("Dans getOneDog...");

    final String id = routingContext.request().getParam("id");

    final Dog dog = dogService.findById(id);

    if (dog == null) {
      final JsonObject errorJsonResponse = new JsonObject();
      errorJsonResponse.put("error", "No dog can be found for the specified id:" + id);
      errorJsonResponse.put("id", id);

      routingContext.response()
          .setStatusCode(404)
          .putHeader("content-type", "application/json")
          .end(Json.encode(errorJsonResponse));
      return;
    }
    routingContext.response()
        .setStatusCode(200)
        .putHeader("content-type", "application/json")
        .end(Json.encode(dog));
  }
  private void createOneDog(final RoutingContext routingContext) {
    LOGGER.info("Dans createOneDog...");
    final JsonObject body = routingContext.getBodyAsJson();
    final String name = body.getString("name");
    final String race = body.getString("race");
    final Integer age = body.getInteger("age");
    // TODO Vérification des champs...
    final Dog dog = new Dog(null, name, race, age);
    final Dog createdDog = dogService.add(dog);
    routingContext.response()
        .setStatusCode(201)
        .putHeader("content-type", "application/json")
        .end(Json.encode(createdDog));
  }
  private void updateOneDog(final RoutingContext routingContext) {
    LOGGER.info("Dans updateOneDog...");
    final String id = routingContext.request().getParam("id");
    final JsonObject body = routingContext.getBodyAsJson();
    final String name = body.getString("name");
    final String race = body.getString("race");
    final Integer age = body.getInteger("age");
    // TODO Vérification des champs...
    final Dog dog = new Dog(id, name, race, age);
    final Dog updatedDog = dogService.update(dog);
    routingContext.response()
        .setStatusCode(200)
        .putHeader("content-type", "application/json")
        .end(Json.encode(updatedDog));
  }
  private void deleteOneDog(final RoutingContext routingContext) {
    LOGGER.info("Dans deleteOneDog...");
    final String id = routingContext.request().getParam("id");
    dogService.remove(id);
    routingContext.response()
        .setStatusCode(200)
        .putHeader("content-type", "application/json")
        .end();
  }
}
App.java
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
package com.ice.articlevertxapi;
import io.vertx.core.Vertx;
public class App {
  public static void main(String[] args) {
    System.out.println("App...");
    final Vertx vertx = Vertx.vertx();
    vertx.deployVerticle(new MyApiVerticle());
  }
}

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Thierry-Leriche-Dessirier. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.