Telechargé par ABDELHAK BENTOUIS

FormationSpringBoot-3-SpringMVC

publicité
Orange
de Spring à Spring Boot
les APIs REST avec Spring Web MVC et OpenFeign
septembre 2020
sommaire
– communication client/serveur
– qu'est-ce qu'une API REST ?
– exemple d'API : ToDoList
– formalisation des APIs avec OpenAPI
– mise en oeuvre
– tests d'API REST
– consommation d'une API REST
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
communication client/serveur
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
communication client/serveur
HTTP : les concepts
HTTP s'appuie sur 4 concepts fondamentaux :
– le binôme requête/réponse
– les URLs
– les verbes
– les codes de statut.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
communication client/serveur
HTTP : les URLs
Les urls sont à la base du fonctionnement de http car elles permettent d'identifier une ressource :
– protocol : le protocole utilisé (http, https, ftp, news, ssh...)
– host : nom de domaine identifiant le serveur (FQDN)
– port : le port utilisé (80 pour http, 443 pour https, 21 pour ftp)
– ressource path : identifiant de la ressource sur le serveur
– query : paramètres de la requête.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
communication client/serveur
HTTP : les verbes
Les verbes permettent de manipuler les ressources identifiées par les URLs. Ceux principalement utilisés sont :
– GET : le client demande à lire une ressource existante sur le serveur
– POST : le client demande la création d'une nouvelle ressource sur le serveur
– PUT : le client demande la mise à jour d'une ressource déjà existante sur le serveur
– DELETE : le client demande la suppression d'une ressource existante sur le serveur.
Ils sont invisibles pour l'utilisateur mais sont envoyés lors des échanges réseaux. Chaque requête est accompagnée d'un verbe pour indiquer
l'action à effectuer sur la ressource ciblée.
GET http://welcome.com.intraorange/
GET http://www.monsite.fr/index.php
POST http://api.orange.com/monappli/users/
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
communication client/serveur
HTTP : les codes de statut
Chaque requête de la part d'un client reçoit une réponse de la part du serveur, comportant un code de statut, pour informer le client du bon
déroulement ou non du traitement demandé.
Ces codes de statut sont rangés par plages numériques :
– 1xx : message d'information provisoire
– 2xx : requête reçue, interprétée, acceptée et traitée avec succès
– 3xx : message indiquant qu'une action complémentaire de la part du client est nécessaire (exemple : redirection vers une autre url)
– 4xx : erreur du serveur du fait des données en entrée envoyées par le client (exemple : authentification, autorisations, paramètres
d'entrée)
– 5xx : erreur du serveur du fait d'un motif interne au serveur (exemple : indisponibilité d'un composant du serveur, erreur inattendue).
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
qu'est-ce qu'une API REST ?
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
qu'est-ce qu'une API REST ?
Une API REST (REpresentational State Transfer) permet à une application d'exposer les services qu'elle offre aux autres applications
(pourvues d'une IHM ou pas).
REST s'articule autour de la notion de ressource :
– une ressource représente n'importe quel concept (une commande, un client, un message...)
– une représentation est un document qui capture l'état actuel d'une ressource (au format Json, XML, pdf...)
– une ressource appartient à une organisation (une entreprise, un service public...)
– une ressource est accessible via une URI.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
qu'est-ce qu'une API REST ?
HATEOAS
HATEOAS (Hypermedia As The Engine Of Application State) est un pilier de REST, permettant la découvrabilité (discoverability) de l'API à
partir d'un point d'entrée unique.
Lorsque le serveur envoie sa réponse (la représentation d'une ressource) au client, il doit également ajouter les liens qui permettront au client
de modifier l'état de la ressource en question ou de naviguer vers d'autres ressources.
Conséquences :
– plus le message est pauvre (représentation sans hyperlien), plus le client doit être intelligent (connaître ce qu'il peut faire à partir de tel
état)
– plus le message est riche (avec hyperliens), moins le client doit être intelligent car il n'a qu'à suivre ce que lui indique le serveur.
Un site web respecte cette logique avec des liens envoyés par le serveur pour naviguer entre les pages (ressources), dans un format (HTML
CSS, images...) lisible facilement par un humain. Entre machines, seules les informations métiers (au format Json par exemple) sont utiles,
avec les liens qui les unient.
Voici un article intéressant sur le sujet.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
qu'est-ce qu'une API REST ?
modèle de maturité de Richardson
Le modèle de Richardson permet de mesurer le degré de maturité d'une API :
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
qu'est-ce qu'une API REST ?
modèle de maturité de Richardson
– niveau 0 :
- utilisation de HTTP servant de transport uniquement
- verbe, URL et code retour uniques
exemple : webservices SOAP
– niveau 1 :
- niveau 0 + URLs différentes pour identifier les ressources
exemple : navigation web
– niveau 2 :
- niveau 1 + verbes HTTP pour manipuler les ressources + codes retour pertinents
exemple : APIs REST classiques
– niveau 3 :
- niveau 2 + HATEOAS (liens)
vraie API REST idéalement
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
1/ identifier les ressources qui constitueront l'API
La plupart du temps on retrouve :
– des ressources entités : concepts manipulés par l'API
– des ressources composites : agrégation de plusieurs ressources entités en une seule
– des ressources collections d'entités ou de composites.
Dans le cas d'une API de ToDoList, on peut imaginer avoir les ressources suivantes :
– entités : ToDoList et ToDoItem
– collections : ToDoLists et ToDoItems.
2/ déterminer les URLs de ces ressources et les liens activables
Pour chaque ressource, il faut déterminer :
– comment y accéder (leur URL)
– les actions autorisées :
- changement d'état de la ressource
- navigation vers une autre ressource.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
Pour représenter plus clairement les ressources et leur cinématique, on peut utiliser un diagramme d'interactions :
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
exemple de représentation JSON / HATEOAS - HAL
Le client fait la requête suivante (point d'entrée de l'application) pour avoir la liste des ToDoList :
GET
/todolists
Le serveur reçoit la requête, la traite (connexion base de données...), et envoie par exemple, pour chaque ToDoList :
– ses informations
– ses liens activables :
- de changement d'état (modify, delete)
- de navigation (self, items...).
Note : par choix de design, les sous-ressources (ToDoItems) peuvent n'être envoyées qu'à la demande, lorsque l'on consulte
l'une des ToDoList. Cela évite de charger une grappe d'objets trop importante.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
exemple de représentation JSON / HATEOAS - HAL
[ # tableau de ToDoLists
{ # première ToDoList de la liste
# infos complètes ou partielles de la ressource ToDoList
"id": 123,
"title": "vacances",
"endDate": "12/10/2018",
"complete": false,
# liens activables pour cette ToDoList (HATEOAS)
"_links":{
"self": {"href": "/todolists/123"},
"all": {"href": "/todolists{?title}"},
"modify": {"href": "/todolists/123"},
"delete": {"href": "/todolists/123"},
"items": {"href": "/todolists/123/todoitems"}
},
# sous ressources éventuelles
"_embedded": {
"todoitems":[ ... ]
}
},
# deuxième ToDoList de la liste
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
{ ... }
]
exemple d'API : ToDoList
exemple de représentation JSON / HATEOAS - HAL
Le client suit le lien self de l'une des ToDoList de la liste pour avoir le détail de cette ressource :
GET
/todolists/123
Le serveur envoie la représentation complète :
– les informations complètes de la ToDoList
– les liens activables sur cette ToDoList :
- de changement d'état (modify, delete)
- de navigation (self, parent, items...)
– éventuellement les informations des sous-ressources (ToDoItems) partielles ou complètes
- éventuellement les liens activables pour ces ToDoItems.
Note : en tant que fournisseur d'API, il faut réfléchir sur les différentes façons d'exposer ses ressources en fonction des possibles
usages des clients :
– obtenir le détail d'une ressource ou seulement un résumé
– paginer les données
– trier/filtrer selon certains champs
– ...
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
exemple de représentation JSON / HATEOAS - HAL
# une seule ToDoList
{
# informations complètes de la ToDoList
"id": 123,
"title": "vacances",
"endDate": "12/10/2018",
"complete": false,
# liens activables pour cette ToDoList (HATEOAS)
"_links": {
"self": { "href": "/todolists/123" },
"all": { "href": "/todolists{?title}" },
"modify": { "href": "/todolists/123" },
"delete": { "href": "/todolists/123" },
"items": { "href": "/todolists/123/todoitems" }
},
# tableau de ToDoItems (voir page suivante)
"_embedded": {
...
}
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
exemple d'API : ToDoList
exemple de représentation JSON / HATEOAS - HAL
...
"_embedded": {
"todoitems": [ # tableau de ToDoItems
# premier ToDoItem
{ # infos et liens activables pour ce ToDoItem
"id": 987,
"taskName": "acheter une casquette",
"done": false,
"_links": {
"self": {"href": "/todolists/123/todoitems/987"},
...
}
},
# ToDoItem suivant
{ # infos et liens activables pour ce ToDoItem
"id": 988,
"taskName": "prendre la creme solaire",
"done": true,
"_links": {...}
}
]
}
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
spécification d'API : fichier OpenAPI json/yaml
OpenAPI est un métalangage standardisé de description des APIs REST, issu du projet Swagger. Un fichier OpenAPI (en JSON ou en YAML)
contient la description complète d'une API :
– informations générales sur l'API : version, titre, description
– les protocoles autorisés (HTTP, HTTPS...)
– les représentations des ressources autorisées (JSON, XML...)
– les URLs des ressources, avec pour chacune d'elles :
- les verbes autorisés
- une description de l'opération (son but, son usage...)
- les paramètres d'entrée
- les réponses possibles avec leurs codes de statut
– la définition des ressources elles-mêmes (objets échangés).
Un ensemble d'outils Swagger s'appuyant sur OpenAPI permettent de générer la documentation, des tests, le squelette de code
client/serveur...
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
écriture d'API : Swagger Editor
Swagger Editor est un outil accessible sur le web. Il est également possible de l'installer en local.
Il permet :
– d'écrire directement le fichier OpenAPI (description de l'API en JSON ou YAML)
– de vérifier sa validité syntaxique
– d'avoir un affichage très lisible et en temps réel, en miroir du code OpenAPI.
Note : un fichier OpenAPI étant un fichier texte, il est possible d'utiliser tout éditeur de texte, mais vous n'aurez pas de validation
syntaxique ni l'affichage miroir temps réel.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
écriture d'API : API Designer
API Designer est une application web interne Orange déployée sur Cloud Foundry. Elle permet :
– de déclarer plusieurs membres pour un projet d'API
– de décrire une API :
- directement en JSON ou YAML (au format OpenAPI)
- via une IHM (pour ceux que le JSON ou le YAML natif rebutent)
– de se baser sur des templates (typiquement : TMForum)
– de la visualiser dans Swagger UI
– de la publier dans l'API Discovery
– de travailler hors connexion avec synchronisation quand le réseau Orange est de nouveau accessible.
Attention : quand on décrit une API directement en OpenAPI ou via l'IHM, on ne peut pas changer de moyen d'édition pour la
faire évoluer.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
consultation et test d'API : Swagger UI
Swagger UI est un outil accessible sur le web, mais aussi en interne pour publier les APIs Orange.
Il permet d'afficher la définition d'une API OpenAPI sous une forme graphique, claire et conviviale. Il suffit de renseigner l'URL d'accès au
fichier OpenAPI déployé préalablement sur un serveur. L'outil Swagger uploader du programme API permet de le faire facilement.
Le but est de faciliter la compréhension et l'adoption de l'API par les clients.
Swagger UI sert d'interface entre le fournisseur et le client de l'API :
– le fournisseur y publie son API (fichier OpenAPI) et la fait pointer sur une plateforme de test ou un serveur de bouchons (exemples :
Mock-server, SoapUI)
– le client la consulte, et peut tester son comportement (paramètres d'entrée, données échangées, statuts de retour...).
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
inspection d'API : Swagger Inspector
Swagger Inspector est un outil accessible sur le web.
Il a 2 fonctionnalités principales :
– servir de client HTTP pour tester une API (comme POSTMAN, RESTer, RESTClient...)
– générer un fichier OpenAPI à partir des API testées :
- lancer les requêtes HTTP souhaitées pour tester l'API
- ces requêtes sont stockées dans l'historique de l'outil et sont facilement accessibles
- sélectionner dans cet historique les requêtes qui seront prises en compte pour la génération du fichier OpenAPI.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
exposition de mocks d'API : Mock-server
Mock-server est une application interne Orange faisant office de serveur de bouchons statiques.
Elle permet de déterminer pour son API :
– les URLs à écouter
– les paramètres d'entrée (resources pour les POST...)
– les réponses prédéfinies
– des informations additionnelles : entêtes d'entrée et de sortie (mot de passe pour authentification, content-type, code de statut...).
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
formalisation des APIs avec OpenAPI
génération de code client/serveur : Swagger Codegen
Swagger Codegen permet de générer du code client et/ou serveur (comme WSDL2Java pour les webservices Soap) à partir du fichier
OpenAPI. Cet outil est utilisable :
– via Swagger Editor, menu Generate Server ou Generate Client
– via un plugin Maven.
Swagger Codegen supporte plusieurs frameworks Java de gestion d'API, tels que Jersey, Spring Web MVC, CXF. D'autres langages sont
également disponibles (PHP, Node.js, Scala, Ruby, Python, Typescript Angular 2...).
La suite de la présentation ne s'appuiera pas sur Swagger Codegen, afin de montrer comment écrire du code Spring MVC pour
exposer des services REST.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
configuration Maven
Dans le fichier pom.xml, il faut avoir les dépendances vers spring-boot-starter-web et spring-boot-starter-hateoas :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
La dépendance spring-boot-starter-web embarque automatiquement Tomcat ainsi que les dépendances pour la gestion des requêtes HTTP
et de la gestion du MVC (Model View Controller) de Spring.
La dépendance spring-boot-starter-hateoas charge la librairie de gestion du HATEOAS par Spring.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Spring Web MVC est un framework qui gère les intéractions client/serveur, que ce soit pour créer des IHM ou des APIs REST.
Spring HATEOAS va nous aider à ajouter des liens aux ressources retournées par Spring Web MVC.
Pour commencer, il faut créer les resources en Java, en étendant la classe org.springframework.hateoas.RepresentationModel pour que
Spring HATEOAS puisse leur ajouter les liens :
@JsonIgnoreProperties(ignoreUnknown = true)
public class ToDoList extends RepresentationModel<ToDoList> {
private long id;
private String title;
...
// getters and setters
...
}
Note : pour les applications très simples (comme la gestion des ToDoList), ces resources peuvent aussi servir de classes
persistantes (@Entity en JPA).
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Puis il faut créer une classe contrôleur REST qui représente l'API :
@RestController
@RequestMapping(value="/api/v1/todolists", produces={APPLICATION_JSON_VALUE})
@CrossOrigin
public class ToDoList_API {
// accès à la couche service de l'application
@Inject
private ToDoList_Service tdlService;
...
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Il faut ensuite créer une méthode par service que l'on veut exposer dans l'API (URL et verbe HTTP : GET, POST...) :
Exemple de traitement de GET http://.../todolists{?title=xxx}, avec passage de filtre optionnel dans l'URL :
@GetMapping()
public List<ToDoList> getAllToDoLists(
@RequestParam(value="title", defaultValue="", required=false) String title) {
List<ToDoList> todolists = tdlService.getAllToDoLists(title);
//pour créer les links HATEOAS
for (ToDoList tdl : todolists) {
addLinksTo_ToDoList(tdl);
}
return todolists;
}
Note : aucun code de statut n'est spécifié, donc code 200 (OK) par défaut.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Exemple de traitement de GET http://.../todolists/xxx/todoitems/yyy, avec passage de paramètres dans l'URL :
@GetMapping("/{listId}/todoitems/{itemId}")
public ToDoItem getToDoItem(@PathVariable long listId, @PathVariable long itemId) {
ToDoItem tdi = tdiService.getToDoItem(itemId);
//pour HATEOAS
addLinksTo_ToDoItem(listId, tdi);
return tdi;
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
2 exemple équivalents de traitement de POST http://.../todolists, avec passage d'un objet dans le body de la requête, avec
code de statut spécifié en réponse :
@PostMapping()
@ResponseStatus(HttpStatus.CREATED)
public ToDoList createToDoList(@RequestBody ToDoList todolist){
return tdlService.createToDoList(todolist);
}
@PostMapping()
public HttpEntity<ToDoList> createToDoList(@RequestBody ToDoList todolist){
ToDoList tdl = tdlService.createToDoList(todolist);
return new ResponseEntity<ToDoList>(tdl, HttpStatus.CREATED);
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
2 exemples de traitement de DELETE http://.../todolists/xxx/todoitems/yyy, avec une méthode retournant void (la deuxième
solution est nécessaire pour HATEOAS) :
@DeleteMapping("/{listId}/todoitems/{itemId}")
public void deleteToDoItem(@PathVariable long listId, @PathVariable long itemId){
ToDoList tdl = tdlService.getToDoList(listId);
tdiService.deleteToDoItem(tdl, itemId);
}
@DeleteMapping("/{listId}/todoitems/{itemId}")
public HttpEntity<Void> deleteToDoItem(@PathVariable long listId, @PathVariable long itemId){
ToDoList tdl = tdlService.getToDoList(listId);
tdiService.deleteToDoItem(tdl, itemId);
return new ResponseEntity<Void>(HttpStatus.OK);
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Exemple d'ajout de liens à une ressource avec Spring HATEOAS :
private void addLinksTo_ToDoList(ToDoList tdl){
//{"rel:"self", "href":"http://localhost:8080/api/v1/todolists/123"}
tdl.add(linkTo(methodOn(ToDoList_API.class).
getToDoList(tdl.getId())).withSelfRel());
//template d'URL
//{"rel:"all", "href":"http://.../api/v1/todolists{?title}"}
tdl.add(linkTo(methodOn(ToDoList_API.class).
getAllToDoLists(null)).withRel("all"));
//vraie URL
//{"rel:"all", "href":"http://.../api/v1/todolists?title=vacances"}
tdl.add(linkTo(methodOn(ToDoList_API.class).
getAllToDoLists(null)).withRel("all").expand("vacances"));
//{"rel:"items", "href":"http://.../api/v1/todolists/123/todoitems"}
tdl.add(linkTo(methodOn(ToDoList_API.class).
getToDoItemsOfList(tdl.getId(), null)).withRel("items"));
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
ATTENTION : pour les méthodes de type void, on peut utiliser linkTo(methodOn(...)) pour ajouter des liens seulement si la méthode retourne
HttpEntity<Void>. Si elle retourne vraiment void, il faut ruser.
Voici 2 exemples équivalents pour ajouter un lien {"rel:"delete", "href":"http://.../todolists/xxx/todoitems/yyy"} :
private void addLinksTo_ToDoItem(long listId, ToDoItem tdi) {
//faire pointer vers une méthode :
//- ne retournant pas void
//- qui a la bonne URL
tdi.add(linkTo(methodOn(ToDoList_API.class).
getToDoItem(listId, tdi.getId())).withRel("delete"));
//recomposer l'URL, pourquoi pas en mixant :
//- une URL d'une méthode ne retournant pas void
//- et la compléter avec ce qu'il manque
tdi.add(linkTo(methodOn(ToDoList_API.class).
getToDoItemsOfList(listId, null)). // "/todolists/xxx/todoitems"
slash(tdi.getId()).withRel("delete")); // "/yyy"
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Un mécanisme générique de gestion d'exceptions peut être mis en oeuvre, pour définir quel code de statut, quel message... renvoyer suivant
le type d'exception rencontré.
@RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public final Error handleEmptyResultException(EmptyResultDataAccessException e) {
return new Error("40001","Resource not exists.",e.getMessage(),null);
}
// idem si on n'a pas besoin de récupérer les infos de l'exception `e`.
// On met la classe de l'exception dans @ExceptionHandler et pas dans
// le paramètre de la méthode
@ExceptionHandler(EmptyResultDataAccessException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public final Error handleEmptyResultExceptionBis() {
return new Error("40001","Resource not exists.", "message", null);
}
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Spring HATEOAS sait gérer 2 formats de liens, soit :
– le format HAL (comme vu précédemment)
– un format propriétaire à la fois plus simple à mettre en oeuvre et plus complet.
C'est ce dernier format qui est utilisé dans les exemples ci-dessus et dans les TPs.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Format HAL :
"_links": {
"<linkName>": {"href": "..."}, // "self": {"href": "..."},
"<linkName>": {"href": "..."}, // "all": {"href": "..."},
...
},
"_embedded": {
"<sous-ressources>": [
// "todoitems": [
{ ... },
{ ... },
...
]
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Format Spring HATEOAS :
"links": [
{ // attributs du premier lien
"<attributName>": "...", // "rel": "self",
"<attributName>": "...", // "href": "...",
...
},
... // autres liens
],
"<sous-ressources>": [
// "todoitems": [
{ ... },
{ ... },
...
]
Les liens sont représentés sous forme de tableau, chaque lien pouvant avoir plusieurs attributs pour le spécifier.
Les sous-ressources sont directement placées à la racine de l'objet, comme les autres informations (id, title...) et non pas dans _embedded.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Au format Spring HATEOAS, les liens peuvent avoir les attributs suivants :
– rel : nom de la relation entre la ressource courante et la ressource ciblée (ex : self, items)
– href : URL qui identifie la ressource ciblée (ex : http://com.orange.monappli/api/v1/todolists)
– hreflang : information sur la langue de la ressource ciblée (ex : fr)
– media : type de média de la ressource ciblée (ex : text, video)
– title : nom du lien compréhensible par un humain, dans la langue hreflang, qu'on peut afficher sur une IHM par exemple (ex :
modification de la ToDoList)
– type : information sur le content-type de la ressource ciblée (ex : application/pdf, text/html; charset=utf-8)
– deprecation : le lien est-il déprécié ou pas.
Seuls rel et href sont obligatoires. Les autres valent null par défaut.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Pour utiliser le format HAL, il suffit de le spécifier au niveau du contrôleur REST :
@RestController
@EnableHypermediaSupport(type = HypermediaType.HAL)
...
Attention : il faut que les méthodes du contrôleur retournent des objets de type :
– RepresentationModel<xxx> : pour les ressources entités (ex : ToDoList)
– EntityModel<xxx> : pour transformer une classe POJO en modèle (sans qu'elle étende RepresentationModel<xxx>)
– CollectionModel<xxx> : pour les collections de ressources
– PagedModel<xxx> : pour les collections paginées de ressources.
Si on retourne List<ToDoList>, on retombe dans le format Spring HATEOAS pour les liens et les sous-ressources.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
implémentation Spring Web MVC / Spring HATEOAS
Pour utiliser le format Spring HATEOAS, 2 solutions :
– dans le fichier application.properties :
# ne pas faire de HAL
spring.hateoas.use-hal-as-default-json-media-type = false
# ne pas retourner les attributs de valeur `null`
spring.jackson.default-property-inclusion = NON_NULL
– dans le contrôleur REST :
@RestController
@EnableAutoConfiguration(exclude = HypermediaAutoConfiguration.class)
...
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
ajout de Swagger-UI dans l'application
Springdoc-openapi permet d'embarquer Swagger-UI directement dans l'application (accessible par défaut à l'URL : <contextroot>/swagger-ui.html), afin d'exposer la documentation OpenAPI des services liés à la version de l'API déployée.
Il faut tout d'abord ajouter la dépendance Maven nécessaire dans le pom.xml :
<properties>
<springdoc-openapi-ui.version>1.4.5</springdoc-openapi-ui.version>
<springdoc-openapi-maven-plugin.version>0.2</springdoc-openapi-maven-plugin.version>
</properties>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc-openapi-ui.version}</version>
</dependency>
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
ajout de Swagger-UI dans l'application
Ensuite, il faut ajouter le plugin Maven permettant de générer le fichier openapi.json à partir des sources :
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>${springdoc-openapi-maven-plugin.version}</version>
<executions>
<execution>
<phase>integration-test</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/api-docs</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>
</plugin>
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
ajout de Swagger-UI dans l'application
Puis il faut modifier la configuration du plugin spring-boot-maven-plugin de base pour que la génération du fichier openapi.json fonctionne :
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>pre-integration-test</id>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>post-integration-test</id>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
ajout de Swagger-UI dans l'application
Enfin, on peut configurer Springdoc-openapi grâce :
– au fichier application.properties :
# package contenant les APIs REST à exposer avec Swagger-UI
springdoc.packagesToScan = com.orange.todolist.api
# URL des APIs REST à exposer avec Swagger-UI
springdoc.pathsToMatch = /api/v1/todolists/**
# swagger-ui custom path
springdoc.swagger-ui.path=/swagger-ui.html
– à la classe contrôleur REST :
// label dans swagger-UI pour regrouper les services exposés
@Tag(name = "ToDoList management")
@RestController
@RequestMapping(value="/api/v1/todolists",produces={APPLICATION_JSON_VALUE})
@CrossOrigin
public class ToDoList_API {
...
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
ajout de Swagger-UI dans l'application
– et à une classe de configuration permettant d'ajouter des informations à l'API :
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI todolistApi() {
return new OpenAPI()
.info(new Info().title("ToDoList API")
.description("API for managing ToDoLists.")
.version("v0.1.0")
.contact(addContact())
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}
private Contact addContact(){
Contact contact = new Contact();
contact.setName("Prenom Nom");
contact.setEmail("[email protected]");
return contact;
}
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
mise en oeuvre
ajout de Swagger-UI dans l'application
Pour avoir Swagger-UI en tant que page d'accueil de l'application, il faut ajouter :
– une classe contrôleur Spring (@Controller)
– une méthode qui va intercepter les requêtes <urlAppli>/ et les rediriger vers la page swagger-ui.html :
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HomeController {
@GetMapping(value = "/")
public ModelAndView home(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("redirect:/swagger-ui.html");
}
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'API REST
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'API REST
exemple de réponse de l'API ToDoList
Voici un exemple de réponse retournée en JSON par un GET /todolists/123 :
{ "id": 123,
"title": "vacances",
"endDate": "12/06/2016",
"complete": false,
"links": [
{"rel":"self", "href":"http://localhost/api/v1/todolists/123"},
{"rel":"all", "href":"http://localhost/api/v1/todolists"},
...
],
"todoitems": [
{"id": 987,
"taskName": "acheter une casquette",
"done": true,
"links": [
{"rel":"self", "href":".../api/v1/todolists/123/todoitems/987"},
{"rel":"allOfList","href":".../api/v1/todolists/123/todoitems"},
...
]
}
]
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : dépendance Maven
Comme pour chaque composant, il faut pouvoir tester l'API REST unitairement, en bouchonnant les éléments dont elle dépend.
Spring MVC Test :
– permet de simplifier l'écriture de tests des @RestController
– intègre JUnit 5 pour écrire les tests unitaires, Hamcrest pour faire des comparaisons de données (equalTo(), hasItems(),
containsString()...) ou encore Mockito (et BDDMockito) pour bouchonner les composants sous-jacents.
Les dépendances nécessaires sont toutes importées par Spring Boot dans le spring-boot-starter-test :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : initialisation
Utilisation de @WebMvcTest(...) plutôt que @SpringBootTest :
– c'est + performant car seul le RestController est chargé, au lieu de toute l'application
– cela oblige à fournir des mocks pour les dépendances du RestController.
@WebMvcTest(ToDoList_API.class)
public class ToDoList_API_Test {
@Inject
private MockMvc mvc;
@MockBean
private ToDoList_Service tdlService;
@MockBean
private ToDoItem_Service tdiService;
//all tests
...
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête GET
Exemple de test d'une requête GET /api/v1/todolists/123 :
@DisplayName("returns a todolist")
@Test
public void shouldReturnRelevantToDoList() throws Exception {
given(tdlService.getToDoList(123)).willReturn(new ToDoList(...));
mvc.perform(
get("/api/v1/todolists/{listId}", 123).contentType(MediaType.APPLICATION_JSON_VALUE)).
andDo(print()).
andExpect(status().is2xxSuccessful()).
andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(jsonPath("title").value("vacances")).
andExpect(jsonPath("complete").value(false)).
andExpect(jsonPath("todoitems").exists()).
andExpect(jsonPath("todoitems").hasSize(2))).
andExpect(jsonPath("todoitems[*].id", contains(987, 361))).
andExpect(jsonPath("links").isNotEmpty()).
andExpect(jsonPath("links[0].rel").exists()).
andExpect(jsonPath("links[0].rel").value("self")).
andExpect(jsonPath("links[0].href").value("http://localhost/api/v1/todolists/123"));
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête GET
Côté Mockito, on dit au mock de tdlService.getToDoList(listId) de retourner la ToDoList initialisée pour le bouchon, si listId passé en
paramètre = 123.
Grâce à andDo(print()), on peut afficher sur la console ce qui se passe :
– requête : verbe, URL, paramètres, headers...
– controller : méthode appelée
– réponse : code de statut, headers, body de la réponse...
On vérifie dans la réponse que :
– le code de statut est de la famille 2xx (200 (OK), 201 (Created)...)
– title="vacances"
– complete=false
– il existe un structure todoitems ayant 2 éléments
– parmi les todoitems, on a bien les id 987 et 361
– que la structure links :
- n'est pas vide
- contient une sous-struture self ayant pour href="http://localhost/api/v1/todolists/123".
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête GET
Exemple de test d'une requête GET /api/v1/todolists :
@DisplayName("returns all todolists")
@Test
public void shouldReturnAllToDoLists() throws Exception {
List<ToDoList> todolists = new ArrayList<ToDoList>();
todolists.add(new ToDoList(...));
...
given(tdlService.getAllToDoLists(ArgumentMatchers.anyString())).willReturn(todolists);
mvc.perform(
get("/api/v1/todolists").contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().isOK()).
andExpect(jsonPath("$", hasSize(2))).
andExpect(jsonPath("$[*].id", contains(123, 45))).
andExpect(jsonPath("$[0].id", is(123)));
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête GET
Côté Mockito, on dit au mock de tdlService.getAllToDoLists(String) de retourner la liste de ToDoList initialisée au préalable, quelque soit la
String passée en paramètre.
On vérifie dans la réponse que :
– code de statut=200 (OK)
– il y a 2 éléments à la racine $
– parmi ces éléments, on a bien les id 123 et 45
– que le 1er élément à la racine a pour id=123.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête GET
Exemple de test d'une requête GET /api/v1/todolists/999 :
@DisplayName("if todolist id is not exist, returns 404")
@Test
public void shouldReturnToDoListNotFoundStatus() throws Exception {
given(tdlService.getToDoList(999)).willThrow(new IllegalArgumentException());
mvc.perform(
get("/api/v1/todolists/{listId}", 999).contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().isNotFound());
}
Côté Mockito, on dit au mock de tdlService.getToDoList(listId) de retourner une IllegalArgumentException, si listId passé en paramètre =
999.
Dans la requête, on passe l'objet ToDoList transformé en JSON, puis on vérifie dans la réponse que :
– code de statut=404 (Not Found).
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête POST
On lance une requête POST /api/v1/todolists :
@DisplayName("creates a todolist with its id")
@Test
public void shouldReturnRelevantToDoListId() throws Exception {
given(tdlService.createToDoList(Matchers.any(ToDoList.class))).willReturn(new ToDoList(...));
mvc.perform(
post("/api/v1/todolists")
.contentType(MediaType.APPLICATION_JSON_VALUE).
.content(object2Json(new ToDoList(...))).
andExpect(status().isCreated()).
andExpect(jsonPath("id").value("1"));
}
Côté Mockito, on dit au mock de tdlService.createToDoList(ToDoList) de retourner une ToDoList bouchonnée, quelque soit la ToDoList
passée en paramètre.
Dans la requête, on passe l'objet ToDoList transformé en JSON, puis on vérifie dans la réponse que :
– code de statut=201 (Created)
– id=1.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests unitaires d'API REST
Spring MVC Test : requête DELETE
On lance une requête DELETE /api/v1/todolists/123 :
@DisplayName("delete a todolist")
@Test
public void shouldReturnOKStatusWhenDeleteToDoList() throws Exception {
doNothing().when(tdlService).deleteToDoList(Matchers.any(Long.class));
mvc.perform(
delete("/api/v1/todolists/{listId}", 123).contentType(MediaType.APPLICATION_JSON_VALUE)).
andExpect(status().isOk());
}
Côté Mockito, on dit au mock de tdlService.deleteToDoList(Long) de ne rien faire, quelque soit l'identifiant passé en paramètre (ne
s'applique que pour bouchonner les méthodes void).
On vérifie dans la réponse que :
– code de statut=200 (OK)
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured
REST-assured est un framework Java permettant de simplifier l'écriture des tests d'intégration d'API, pour simuler les requêtes lancées par un
client et vérifier les réponses renvoyées par l'API. Toute l'application est testée de bout en bout.
Il se base sur le formalisme BDD (given/when/then) et s'intègre bien avec Hamcrest.
Il supporte :
– les principaux verbes HTTP
– les statuts de retour
– l'authentification (Basic authentication, Digest, Form, OAuth...)
– le passage de paramètres à une requête (dans l'URL, ou via un formulaire)
– la validation des données reçues dans le corps du message
– les headers HTTP (contrôle du cache, ETag, format de données supportés...)
– les cookies
– la configuration de proxy
– le paramétrage des logs de tests (toutes les informations, ou seulement le body, les paramètres, les headers...)
– la sérialisation/désérialisation entre objets Java et messages JSON
– ...
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured : dépendance Maven
Pour utiliser REST-assured, il faut l'ajouter en tant que dépendance Maven dans le pom.xml, avec le scope test :
<properties>
...
<rest-assured.version>4.3.1</rest-assured.version>
<groovy.version>3.0.2</groovy.version>
</properties>
...
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
Attention : il faut spécifier la version de Groovy utilisée par Spring Boot, sinon par défaut, Spring Boot utilise une version trop
ancienne pour Rest-Assured 4.3.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured : initialisation
import static io.restassured.parsing.Parser.JSON;
import static io.restassured.RestAssured.expect;
import static io.restassured.RestAssured.given;
...
@SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ToDoListApplication_IT {
@LocalServerPort
private int port;
@BeforeEach
public void setUp() {
RestAssured.baseURI = "http://localhost";
RestAssured.port = port;
RestAssured.basePath = "/api/v1/todolists";
RestAssured.defaultParser = JSON;
RestAssured.requestSpecification = given().contentType("application/json; charset=utf-8");
}
//all tests
...
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured : requête GET
Exemple de test d'une requête GET /api/v1/todolists/123 :
@Test
public void getTodoList_OK() throws Exception {
given().
when().
get("/{listId}", 123).
then().
statusCode(200).
body("title", equalTo("vacances")).
body("complete", equalTo(false)).
body("todoitems.id", hasItems(987, 361));
}
On vérifie dans la réponse que :
– code de statut = 200 (OK)
– title="vacances"
– complete=false
– parmi les todoitems, on a bien les id 987 et 361.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured : requête GET avec paramètre
Exemple de test d'une requête GET /api/v1/todolists?title=vacances :
@Test
public void getTodoListsWithParameter_OK() throws Exception {
given().
when().
get("?title={title}", "vacances").
then().
statusCode(200).
body("size()", equalTo(1));
}
On vérifie dans la réponse que :
– code de statut = 200 (OK)
– la taille de la liste = 1.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured : requête POST
Exemple de test d'une requête POST /api/v1/todolists :
@Test
public void postTodoList_OK() throws Exception {
ToDoList tdl = new ToDoList(...);
given().
body(tdl).
when().
post().
then().
statusCode(201);
}
On passe la ToDoList à créer dans le body de la requête.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
REST-assured : autres exemples pratiques
// vérifier que la ressource /todolists est conforme à un schéma
given().when().get().then().assertThat().body(matchesJsonSchemaInClasspath("todolists-schema.json"));
// récupérer une valeur dans le corps ou le header de la réponse
Response res = given().when().get("/{listId}", 123).then().statusCode(200).extract().response();
String title = res.path("title");
String selfLink = res.path("links[0].href");
String headerValue = res.header("headerParam");
String json = given().when().get("/{listId}", 123).asString();
long id = from(json).getLong("id");
List<String> itemIds = from(json).get("todoitems.id");
// s'authentifier avant de lancer une requête en particulier
given().auth().basic(username, password).when()...
// s'authentifier pour toutes les requêtes (à mettre dans setUp())
RestAssured.authentication = basic("username", "password");
Il existe de nombreuses autres fonctionnalités de test offertes par REST-assured.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate
Karate est un framework open-source se basant également sur le formalisme given/when/then, et permettant notamment d'écrire des tests
d'intégration d'API sans code Java. Toute l'application est testée de bout en bout.
Il permet aussi de lancer les tests en parallèle pour de meilleures performances, ou encore de faciliter le test d'appels asynchrones (en cas
d'erreur, relancer N fois ce test avec X secondes entre chaque lancement, avant de tomber réellement en erreur).
L'outil parle nativement le JSON (et le XML), ce qui facilite l'écriture des tests et des jeux de données.
Il permet en plus d'appeler du code Java ou Javascript.
Comme tout outil de test, il s'intègre très bien avec JUnit.
Attention : Karate nécessite au moins un JDK 8_112 pour fonctionner.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : dépendances Maven
Pour utiliser Karate, il faut l'ajouter en tant que dépendance Maven dans le pom.xml, avec le scope test :
<properties>
...
<karate.version>0.9.6</karate.version>
</properties>
...
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-apache</artifactId>
<version>${karate.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.intuit.karate</groupId>
<artifactId>karate-junit5</artifactId>
<version>${karate.version}</version>
<scope>test</scope>
</dependency>
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : initialisation
Il faut créer une classe Java de test pour lancer les tests Karate :
import com.intuit.karate.junit5.Karate;
public class ToDoListApplication_KarateTest {
@BeforeAll
public static void before() {
ToDoListApplication.main(new String[]{"--server.port=8080"});
}
@Karate.Test
Karate testApiFeature() {
return Karate.run("classpath:karate/features/api.feature");
}
}
L'annotation @BeforeAll permet de lancer au préalable l'application en lui précisant sur quel port. On connait ainsi l'URL sur laquelle pointer
(http://localhost:8080).
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : initialisation
Par convention, placer à la racine des tests src/test/resources un fichier karate-config.js qui permet de paramétrer les tests Karate :
function fn() {
karate.configure('connectTimeout', 5000);
karate.configure('readTimeout', 5000);
karate.configure('headers',{'Content-Type':'application/json; charset=utf-8'});
var config = {
baseUrl: 'http://localhost:8080/api/v1/todolists'
}
return config;
}
Il est possible par exemple de changer l'URL de base de l'API à tester en fonction d'une variable d'environnement.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : création des features
Pour écrire des tests Karate, il faut créer des fichiers xxx.feature, par exemple dans le répertoire src/test/resources/karate/features.
Dans ces fichiers seront décrits les scénarii de tests :
# description de l'objectif du fichier (de la feature)
Feature: todolist basic features
# définition des variables utilisées par tous les scénarii du fichier
Background:
* url baseUrl
* def todolist = {title:'vacances', endDate:'2018-11-09', complete:false}
# vient ensuite la liste des scénarii (slides suivants).
...
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : création des features
Exemple de test d'une requête GET /api/v1/todolists :
# création d'un tag (optionnel) pour réutiliser ce scénario ailleurs
@getAllToDoLists
# nouveau scénario
Scenario: get all ToDoLists list
Given path '/'
When method GET
Then status 200
And assert $.length == 2
And match $ contains { id: '#number', title: 'vacances',
endDate:'2018-11-09', complete:false, todoitems:[], links: '#notnull'}
And match $ contains { id: '#number', title: 'noel',
endDate:'2018-11-09', complete:false, todoitems:[], links: '#notnull'}
On s'assure que la réponse :
– a un code statut 200
– qu'elle comporte 2 éléments qui respectent les critères Json définis.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : création des features
Exemple de test d'une requête GET /api/v1/todolists/123 :
@getToDoList
Scenario: get a ToDoList
Given path '/' + __arg # argument passé lors de l'appel du scénario
When method GET
Then status 200
And match $.title == 'vacances'
And match $.todoitems == []
And assert todolist.links.length == 5
And match todolist.links[*].rel contains ['self', 'all', 'delete', 'modify', 'items']
On s'assure que la réponse :
– a un code statut 200
– a un title = vacances
– a un tableau de todoitems vide
– a une liste de 5 links, ayant ces différentes valeurs d'attribut rel.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : création des features
Exemple de test d'une requête POST /api/v1/todolists :
@createToDoList
Scenario: create a ToDoList
Given path '/'
And request todolist # variable todolist du Background
When method POST
Then status 201
And match $.id == '#notnull'
And def id = $.id
On s'assure que la réponse :
– a un code statut 201
– a un id renseigné.
On définit également une variable id contenant la valeur de l'attribut id retournée par l'API.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : réutilisation des features
Pour des scénarii complets, on a souvent besoin d'utiliser les mêmes features de base. On peut écrire celles-ci dans un ou des fichiers
xxx.feature, et nos scénarii dans d'autres fichiers xxx.feature.
Exemple de réutilisation du scénario @getToDoList (du fichier todolist.feature) dans un scénario plus complet (du fichier
api.feature) :
#api.feature
Scenario: create a ToDoList, then get it, then delete it
# création de la ToDoList (voir slide précédent)
...
# appel du scénario `todolist.feature@getToDoList` en passant
# en paramètre la variable `id` définie dans le POST précédent
* def r = call read('todolist.feature@getToDoList') {id:'#(id)'}
* match r.response.id == id
# suppression de la ToDoList
...
Attention : préciser le tag du scénario appelé, sinon tous ceux du fichier todolist.feature seront exécutés.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
Karate : rapport de tests
A la fin des logs de tests, Karate vous fournit une URL pour visualiser le rapport des tests :
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
autres outils de tests
Une API est accessible sur le web, donc depuis un navigateur classique (effectue un GET sur l'URL).
Pour les autres verbes, il faut utiliser un outil plus complet, par exemple :
– POSTMAN en version standalone (ancien plugin pour Chrome)
– Newman, version de POSTMAN en ligne de commandes (simplifie l'intégration continue)
– RESTer plugin pour Firefox et Chrome
– RESTClient plugin pour Firefox
– Swagger Inspector
– SoapUI en stand alone ou plugin intégré à l'IDE
Ces outils permettent :
– de lancer des requêtes suivant les verbes les plus courants (GET, POST, PUT, DELETE...)
– d'ajouter les informations JSON attendues en entrée (pour un POST par exemple)
– d'ajouter les Headers qu'il faut, ou les informations d'authentification
– d'affficher les entêtes de réponse (code de statut, content-type...)
– d'afficher le corps de la réponse
– ...
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
tests d'intégration d'API REST
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Les APIs REST se multipliant, la plupart des applications sont (ou seront) amenées à en fournir mais aussi en être clientes.
Spring Cloud Open Feign est l'intégration d'Open Feign avec Spring Boot.
Open Feign est un client HTTP open-source permettant de facilement consommer des APIs REST. Il s'intègre parfaitement avec les principaux
outils pour gérer des microservices dans un environnement Cloud :
– Ribbon : load balancer (côté client) de microservices
– Eureka : service discovery de microservices
– Hystrix : circuit breaker
– ...
Voici un article intéressant sur les possibilités d'Open Feign.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : dépendances Maven
Il faut d'abord ajouter les dépendances Maven nécessaires :
<springcloud.version>Hoxton.SR8</springcloud.version>
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : configuration Spring Boot
Puis il faut ajouter l'annotation @EnableFeignClients dans la classe principale de l'application :
@SpringBootApplication
@EnableFeignClients("com.orange.todolist.apiconsumer")
public class ToDoListApplication {
public static void main(String[] args) {
SpringApplication.run(ToDoListApplication.class, args);
}
}
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : création du client
Puis, il faut créer les interfaces clientes (proxy) des APIs REST ciblées, annotées @FeignClient :
@FeignClient(name="mypartner", url="${partner.api.url}/api/v3/mypartner")
public interface OpenFeignClientOfPartner {
...
}
Parmi les attributs possibles de @FeignClient :
– name (ou value) :
- dans un environnement Cloud avec Eureka, Ribbon... ce nom est défini dans le fichier application.properties du microservice
cible : spring.application.name=mypartner
- pour un appel direct, le nom est libre
– url :
- URL du partenaire.
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : création du client
Enfin il faut créer les signatures de méthodes pour consommer les APIs du partenaire. On retrouve les annotations utilisées pour exposer les
APIs REST avec Spring Web MVC (@GetMapping...) :
Exemple d'appel de GET <urlPartner>/todolists ou GET <urlPartner>/todolists?title=vacances :
@GetMapping("/todolists")
List<ToDoList> getAllToDoListsFromPartner(@RequestParam("title") String title);
Exemple d'appel de GET <urlPartner>/todolists/123 :
@GetMapping("/todolists/{listId}")
ToDoList getToDoListFromPartner(@PathVariable("listId") long listId);
Exemple d'appel avec un header d'authentification :
@GetMapping("/todolists/{listId}")
ToDoList getToDoListFromPartner(
@RequestHeader("X-Auth-Token") String authHeader,
@PathVariable("listId") long listId);
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : création du client
Exemple d'appel de POST <urlPartner>/todolists :
@PostMapping("/todolists")
ToDoList createToDoListToPartner(@RequestBody ToDoList tdl);
Exemple d'appel de PUT <urlPartner>/todolists/123 :
@PutMapping("/todolists/{listId}")
ResponseEntity<Void> modifyToDoListToPartner(@PathVariable("listId") long listId,
@RequestBody ToDoList tdl);
Exemple d'appel de DELETE <urlPartner>/todolists/123 :
@DeleteMapping("/todolists/{listId}")
ResponseEntity<Void> deleteToDoListToPartner(@PathVariable("listId") long listId);
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : configuration du client
Open Feign donne la possibilité d'affiner la configuration globale des différents clients (timeout, niveau de log...) :
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 3000
loggerLevel: basic
decode404: true
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
consommation d'une API REST
Open Feign : configuration du client
On peut aussi paramétrer certains clients spécifiquement :
feign:
client:
config:
<feignName>:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.orange.todolist.MySimpleErrorDecoder
retryer: com.orange.todolist.MySimpleRetryer
requestInterceptors:
- com.orange.todolist.FooRequestInterceptor
- com.orange.todolist.BarRequestInterceptor
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
documentation
– Site Swagger et spécifications Swagger
– Documentation Spring MVC
– Documentation Spring HATEOAS et migration Spring HATEOAS 0.x vers 1.x
– Documentation Spring MVC Test
– GitHub Open Feign et documentation Spring Cloud Open Feign
– Tutoriel Open Feign, article sur l'utilisation d'Open Feign
– Site Springdoc-openapi
– Migration Springfox vers Springdoc-openapi
– GitHub Karate
– Site REST-assured
– Site Hamcrest
– Site Mockito
– Site SoapUI
– Site Baeldung : de nombreux tutoriaux
de Spring à Spring Boot - les APIs REST avec Spring Web MVC et OpenFeign (Orange internal)
merci
Téléchargement