Introduzione al Framework Web funzionale nella primavera 5

1. Introduzione

Spring WebFlux è un nuovo framework web funzionale costruito utilizzando principi reattivi.

In questo tutorial impareremo come lavorarci in pratica.

Baseremo questo sulla nostra guida esistente a Spring 5 WebFlux. In quella guida, abbiamo creato una semplice applicazione REST reattiva utilizzando componenti basati su annotazioni. Qui, useremo invece il framework funzionale.

2. Dipendenza da Maven

Avremo bisogno della stessa dipendenza spring-boot-starter-webflux definita nell'articolo precedente:

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

3. Framework web funzionale

Il framework web funzionale introduce un nuovo modello di programmazione in cui utilizziamo le funzioni per instradare e gestire le richieste.

Al contrario del modello basato sull'annotazione in cui usiamo le mappature delle annotazioni, qui useremo HandlerFunction e RouterFunction s.

Allo stesso modo, come nei controller annotati, l'approccio degli endpoint funzionali è costruito sullo stesso stack reattivo.

3.1. HandlerFunction

Il HandlerFunction rappresenta una funzione che genera risposte per le richieste indirizzate a loro:

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

Questa interfaccia è principalmente una funzione , che si comporta in modo molto simile a un servlet.

Sebbene, rispetto a un servizio Servlet # standard (ServletRequest req, ServletResponse res) , HandlerFunction non accetta una risposta come parametro di input.

3.2. RouterFunction

RouterFunction funge da alternativa all'annotazione @RequestMapping . Possiamo usarlo per instradare le richieste alle funzioni del gestore:

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

Tipicamente, possiamo importare la funzione di supporto RouterFunctions.route () per creare rotte, invece di scrivere una funzione router completa.

Ci consente di instradare le richieste applicando un RequestPredicate. Quando il predicato viene trovato, viene restituito il secondo argomento, la funzione del gestore:

public static  RouterFunction route( RequestPredicate predicate, HandlerFunction handlerFunction)

Poiché il metodo route () restituisce una RouterFunction , possiamo concatenarla per creare schemi di routing potenti e complessi.

4. Applicazione REST reattiva che utilizza il Web funzionale

Nella nostra guida precedente, abbiamo creato una semplice applicazione REST EmployeeManagement utilizzando @RestController e WebClient.

Ora, implementiamo la stessa logica utilizzando le funzioni router e gestore.

In primo luogo, abbiamo bisogno di creare percorsi utilizzando RouterFunction di pubblicare e consumare i nostri flussi reattivi di Employee s .

Le rotte sono registrate come bean Spring e possono essere create all'interno di qualsiasi classe di configurazione.

4.1. Risorsa singola

Creiamo il nostro primo percorso utilizzando RouterFunction che pubblica una singola risorsa Employee :

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

Il primo argomento è un predicato di richiesta. Notare come abbiamo utilizzato un metodo RequestPredicates.GET importato staticamente qui. Il secondo parametro definisce una funzione del gestore che verrà utilizzata se il predicato si applica.

In altre parole, l'esempio precedente instrada tutte le richieste GET per / dipendenti / {id} al metodo EmployeeRepository # findEmployeeById (String id) .

4.2. Risorsa raccolta

Successivamente, per pubblicare una risorsa di raccolta, aggiungiamo un altro percorso:

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

4.3. Aggiornamento risorsa singola

Infine, aggiungiamo un percorso per l'aggiornamento della risorsa Employee :

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

5. Composizione delle rotte

Possiamo anche comporre i percorsi insieme in un'unica funzione di router.

Vediamo come combinare i percorsi creati sopra:

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

Here, we've used RouterFunction.and() to combine our routes.

Finally, we've implemented the complete REST API needed for our EmployeeManagement application, using routers and handlers.

To run the application, we can either use separate routes or the single, composed one that we created above.

6. Testing Routes

We can use WebTestClient to test our routes.

To do so, we first need to bind the routes using the bindToRouterFunction method and then build the test client instance.

Let's test our getEmployeeByIdRoute:

@Test public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() { WebTestClient client = WebTestClient .bindToRouterFunction(config.getEmployeeByIdRoute()) .build(); Employee employee = 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(employee); }

and similarly getAllEmployeesRoute:

@Test public void whenGetAllEmployees_thenCorrectEmployees() { WebTestClient client = WebTestClient .bindToRouterFunction(config.getAllEmployeesRoute()) .build(); List employees = Arrays.asList( new Employee("1", "Employee 1"), new Employee("2", "Employee 2")); Flux employeeFlux = Flux.fromIterable(employees); given(employeeRepository.findAllEmployees()).willReturn(employeeFlux); client.get() .uri("/employees") .exchange() .expectStatus() .isOk() .expectBodyList(Employee.class) .isEqualTo(employees); }

We can also test our updateEmployeeRoute by asserting that our Employee instance is updated via EmployeeRepository:

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

For more details on testing with WebTestClient please refer to our tutorial on working with WebClient and WebTestClient.

7. Summary

In this tutorial, we introduced the new functional web framework in Spring 5 and looked into its two core interfaces – RouterFunction and HandlerFunction. We also learned how to create various routes to handle the request and send the response.

Inoltre, abbiamo ricreato la nostra applicazione EmployeeManagement introdotta nella guida a Spring 5 WebFlux con il modello degli endpoint funzionali.

Come sempre, il codice sorgente completo può essere trovato su Github.