FormationSpringBoot 3 SpringMVC
FormationSpringBoot 3 SpringMVC
FormationSpringBoot 3 SpringMVC
2020
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 :
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 :
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é.
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).
– 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.
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
– niveau 1 :
- niveau 0 + URLs différentes pour identifier les ressources
– niveau 2 :
- niveau 1 + verbes HTTP pour manipuler les ressources + codes retour pertinents
– niveau 3 :
- niveau 2 + HATEOAS (liens)
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
Dans le cas d'une API de ToDoList, on peut imaginer avoir les ressources suivantes :
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,
},
# 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
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 :
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,
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 :
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 :
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 :
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 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.
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 :
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.
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> {
...
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 {
...
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) {
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) {
//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){
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){
@DeleteMapping("/{listId}/todoitems/{itemId}")
public HttpEntity<Void> deleteToDoItem(@PathVariable long listId, @PathVariable long itemId){
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 :
//{"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.
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);
}
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 :
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 :
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 :
@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 : <context-
root>/swagger-ui.html), afin d'exposer la documentation OpenAPI des services liés à la version de l'API déployée.
<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 :
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")));
}
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 :
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.
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.
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 :
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.
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 :
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 :
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 :
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 :
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).
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 supporte :
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));
}
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));
}
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);
}
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"));
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.
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;
@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.
...
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 :
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
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
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
# 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 :
– 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 :
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 {
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 {
...
}
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...) :
@GetMapping("/todolists")
List<ToDoList> getAllToDoListsFromPartner(@RequestParam("title") String title);
@GetMapping("/todolists/{listId}")
ToDoList getToDoListFromPartner(@PathVariable("listId") long listId);
@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);
@PutMapping("/todolists/{listId}")
ResponseEntity<Void> modifyToDoListToPartner(@PathVariable("listId") long listId,
@RequestBody ToDoList tdl);
@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