Introduction au framework Web fonctionnel au printemps 5

Introduction au framework Web fonctionnel au printemps 5

1. introduction

Le framework Spring WebFlux introduit un nouveau framework Web fonctionnel construit en utilisant des principes réactifs.

Dans ce didacticiel, nous allons apprendre à utiliser ce framework dans la pratique.

Nous allons baser cela sur notre tutoriel existantGuide to Spring 5 WebFlux. Dans ce guide, nous avons créé une petite application REST réactive utilisant des composants basés sur des annotations. Ici, nous utiliserons plutôt le cadre fonctionnel.

2. Dépendance Maven

Nous aurons besoin des mêmes dépendancesspring-boot-starter-webflux que celles définies dans l'article précédent:


    org.springframework.boot
    spring-boot-starter-webflux
    2.0.3.RELEASE

3. Framework Web fonctionnel

Le cadre Web fonctionnel introduit un nouveau modèle de programmation dans lequel nous utilisons des fonctions pour acheminer et gérer les demandes.

Contrairement au modèle basé sur les annotations où nous utilisons des mappages d'annotations, nous utiliserons iciHandlerFunction etRouterFunctions.

En outre, le cadre Web fonctionnel est construit sur la même pile réactive sur laquelle le cadre réactif basé sur des annotations a été construit.

3.1. HandlerFunction

LeHandlerFunction représente une fonction qui génère des réponses pour les demandes qui leur sont acheminées:

@FunctionalInterface
public interface HandlerFunction {
    Mono handle(ServerRequest request);
}

Cette interface est principalement unFunction<Request, Response<T>>, qui se comporte très bien comme une servlet.

Bien que, comparé à un servlet standardServlet.service(ServletRequest req, ServletResponse res),HandlerFunction renvoie la réponse au lieu de la prendre comme paramètre, ce qui la rend sans effet secondaire et plus facile à tester et à réutiliser.

3.2. RouterFunction

RouterFunction sert d'alternative à l'annotation@RequestMapping. Il est utilisé pour acheminer les requêtes entrantes vers les fonctions de gestionnaire:

@FunctionalInterface
public interface RouterFunction {
    Mono> route(ServerRequest request);
    // ...
}

En général, nous pouvons importerRouterFunctions.route(), une fonction d'assistance pour créer des routes, au lieu d'écrire une fonction de routeur complète.

Il nous permet d'acheminer les requêtes en appliquant unRequestPredicate. Lorsque le prédicat est mis en correspondance, le deuxième argument, la fonction de gestionnaire, est retourné:

public static  RouterFunction route(
  RequestPredicate predicate,
  HandlerFunction handlerFunction)

En renvoyant unRouterFunction,route() peut être chaîné et imbriqué pour créer des schémas de routage puissants et complexes.

4. Application REST réactive utilisant le Web fonctionnel

Dans notreguide to Spring WebFlux tutorial, nous créons une petite applicationEmployeeManagement REST en utilisant le sable annoté@RestController WebClient. 

Maintenant, construisons la même application en utilisant les fonctionsRouter etHandler.

Pour commencer, leswe’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. Routes sont enregistrés en tant que Spring beans et peuvent être créés dans n'importe quelle classe qui sera utilisée comme configuration Spring.

4.1. Ressource unique

Créons notre première route en utilisantRouterFunction qui publie une seule ressourceEmployee:

@Bean
RouterFunction getEmployeeByIdRoute() {
  return route(GET("/employees/{id}"),
    req -> ok().body(
      employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));
}

Pour le décomposer, le premier argument définit une requête HTTP GET qui invoquera lehandler. I.e. retourne lesEmployee correspondants deemployeeRepository i le chemin est/employee/{id} and la requête est de type GET.

4.2. Ressource de collection

Ensuite, pour publier la ressource de collection, ajoutons un autre itinéraire:

@Bean
RouterFunction getAllEmployeesRoute() {
  return route(GET("/employees"),
    req -> ok().body(
      employeeRepository().findAllEmployees(), Employee.class));
}

4.3. Mise à jour d'une ressource unique

Enfin, ajoutons un itinéraire pour mettre à jour la ressourceEmployee :

@Bean
RouterFunction updateEmployeeRoute() {
  return route(POST("/employees/update"),
    req -> req.body(toMono(Employee.class))
              .doOnNext(employeeRepository()::updateEmployee)
              .then(ok().build()));
}

5. Itinéraires de composition

Nous pouvons également composer les routes ensemble dans une seule fonction de routeur.

Combinons nos itinéraires créés ci-dessus:

@Bean
RouterFunction composedRoutes() {
  return
    route(GET("/employees"),
      req -> ok().body(
        employeeRepository().findAllEmployees(), Employee.class))

    .and(route(GET("/employees/{id}"),
      req -> ok().body(
        employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)))

    .and(route(POST("/employees/update"),
      req -> req.body(toMono(Employee.class))
        .doOnNext(employeeRepository()::updateEmployee)
        .then(ok().build())));
}

Ici, nous avons utiliséRouterFunction.and() pour combiner nos itinéraires.

Enfin, nous avons créé toutes les API REST en utilisantRouter etHandler t dont nous avions besoin pour notre applicationEmployeeManagement . Pour exécuter notre application, nous pouvons utiliser différents itinéraires ou un itinéraire composé que nous avons créé ci-dessus.

6. Itinéraires d'essai

Nous pouvons utiliserWebTestClient pour tester nos routes.

Pour tester nos routes avecWebTestClient we devez lier nos routes en utilisantbindToRouterFunction and, construisez notre instance de client de test.

Testons nosgetEmployeeByIdRoute:

@Test
public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {
    WebTestClient client = WebTestClient
        .bindToRouterFunction(config.getEmployeeByIdRoute())
        .build();

    Employee expected = new Employee("1", "Employee 1");
    given(employeeRepository.findEmployeeById("1")).willReturn(Mono.just(employee));
    client.get()
        .uri("/employees/1")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(Employee.class)
        .isEqualTo(expected);
}

et de mêmegetAllEmployeesRoute:

@Test
public void whenGetAllEmployees_thenCorrectEmployees() {
    WebTestClient client = WebTestClient
        .bindToRouterFunction(config.getAllEmployeesRoute())
        .build();

    List employeeList = new ArrayList<>();

    Employee employee1 = new Employee("1", "Employee 1");
    Employee employee2 = new Employee("2", "Employee 2");

    employeeList.add(employee1);
    employeeList.add(employee2);

    Flux employeeFlux = Flux.fromIterable(employeeList);
    given(employeeRepository.findAllEmployees()).willReturn(employeeFlux);

    client.get()
        .uri("/employees")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBodyList(Employee.class)
        .isEqualTo(employeeList);
}

Nous pouvons également tester nosupdateEmployeeRoute en affirmant que l'instanceEmployee mise à jour est mise à jour viaEmployeeRepository:

@Test
public void whenUpdateEmployee_thenEmployeeUpdated() {
    WebTestClient client = WebTestClient
        .bindToRouterFunction(config.updateEmployeeRoute())
        .build();

    Employee employee = new Employee("1", "Employee 1 Updated");

    client.post()
        .uri("/employees/update")
        .body(Mono.just(employee), Employee.class)
        .exchange()
        .expectStatus()
        .isOk();

    verify(employeeRepository).updateEmployee(employee);
}

Pour plus de détails sur les tests avecWebTestClient, veuillez consulter notre tutoriel surworking with WebClient and WebTestClient.

7. Sommaire

Dans ce tutoriel, nous avons présenté le nouveau framework Web fonctionnel au printemps 5, nous avons examiné ses deux fonctions de baseRouter andHandler and, nous avons appris à créer diverses routes pour gérer la demande et envoyer la réponse.

De plus, nous avons recréé notre applicationEmployeeManagement introduite dansGuide to Spring 5 WebFlux avec notre nouveau framework.

En posant ses fondations surReactor, le cadre réactif brillerait pleinement avec un accès réactif aux magasins de données. Malheureusement, la plupart des magasins de données ne fournissent pas encore un tel accès réactif, à l'exception de quelques bases de donnéesNoSQL telles queMongoDB.

Comme toujours, le code source complet peut être trouvéover on Github.