Spring WebFlux: een introductie

Spring 5 is het eerste Spring-framework dat ondersteuning voor reactive programming inbouwt. In deze blog gaan we aan de slag met het nieuwe functionele programmeermodel in Spring WebFlux.

Eind 2017 trokken zeven Spring-specialisten van Faros naar San Francisco voor SpringOne. Opvallend: in de vele presentaties over Spring was het allemaal reactive programming wat de klok sloeg. Dat heeft alles te maken met de release van Spring 5 in september van 2017. Spring 5 is de eerste in een reeks releases van Spring-frameworks die ondersteuning voor reactive programming inbouwen.

In deze blog bekijken we de nieuwe WebFlux-module in Spring 5 die ondersteuning biedt voor non-blocking/async HTTP requests volgens de principes van het reactive streams manifesto. De introductie is gebaseerd op ‘New in Spring Framework 5.0: Functional Web Framework’, een presentatie die we volgden tijdens SpringOne.

Reactive programming

Spring 5 trekt volop de kaart van reactive programming. Dat model combineert een asynchroon programmeermodel voor efficiënter gebruik van resources (lees: threads) met de afhandeling van (grote) datastromen. Het gebruikt hiervoor onder andere de concepten publisher/subscriber en backpressure, om te vermijden dat publishers hun subscribers overspoelen met gegevens.

 

 

Belangrijk is het reactive model op alle lagen door te trekken: van database tot web. In deze blog focussen we op het webgedeelte met Spring WebFlux. (Meer info over reactive programming vind je hier: Reactive programming, the basics.)

Spring WebFlux

De voorbije tijd zagen we de opkomst van een nieuw type servers, zoals Netty en Undertow. Zij nemen de servlet-specificatie niet langer als basis, maar werken veel meer low level en omarmen daarbij het idee van non-blocking en asynchrone communicatie. De meerderheid van de bestaande servers en applicaties is echter nog altijd op de servlet API gebaseerd. Die kunnen we niet zomaar overboord gooien.

 

Spring WebFlux kan met beide overweg. Om dat mogelijk te maken is er een nieuwe abstractie gemaakt – HTTP/Reactive Streams – die ook een bridge biedt met de Servlet API 3.1+. Eerdere servlet-versies zijn niet mogelijk omdat zij het asynchrone non-blocking model niet of onvoldoende ondersteunen. 

 

Het asynchrone deel zit dus al goed. Maar wat met het concept van gegevensstromen, waarbij gegevens worden doorgestuurd zodra ze beschikbaar zijn? Traditionele HTTP requests worden geïnitieerd vanuit de client, maar in een reactive model willen we de gegevens vanuit de publisher (server) laten doorstromen naar de client, zodra die beschikbaar zijn. Om dergelijke client/server gegevensstromen op te zetten, maakt Spring WebFlux optimaal gebruik van technieken zoals Server Sent Events en websockets. De combinatie van het efficiënte (her)gebruik van threads door async/callbacks in combinatie met client/server gegevensstromen stelt ons in staat om een compleet nieuw webmodel te bouwen: Spring WebFlux.

 

Aan de slag! In Spring Boot voegen we WebFlux toe met volgende starter:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-WebFlux</artifactId>

</dependency>

Functioneel programmeermodel

Spring WebFlux biedt de keuze uit twee programmeermodellen: een model met annotaties en functioneel model.

 

Het bekende Spring Web MVC-model met annotaties (zoals @Controller) is een declaratief model met callbacks.

 

@RestController

@RequestMapping(“/person”)

public class PersonController {

    @GetMapping(“/{id}”)

    public Mono<Person> getPersonById(@PathVariable String id){

return Mono.just(new Person(id));

}

}

 

Merk hierbij het gebruik op van het nieuw type Mono. Samen met Flux vormt Mono de basistypes in het project Reactor, het onderliggende reactive framework in Spring 5. Mono is een type publisher met één resultaat, Flux is een publisher die verschillende resultaten kan geven.

 

Het tweede, functionele programmeermodel gebruikt Java 8 Lambda’s om routes te definiëren. Het voornaamste verschil met het vorige, declaratieve model is dat de code de afhandeling van requests volledig definieert, inclusief de opbouw van de response body

 

@EnableWebFlux

@Configuration

public class MyConfiguration {

   

    @Bean

    RouterFunction<ServerResponse> myRouterFunction() {

        return route(GET(“/person/{id}”),

                        request ->

                            ServerResponse.ok()

                                    .body(Mono.just(new Person(request.pathVariable(“id”))), Person.class));

    }

}

 

@EnableWebFlux configureert daarbij de nodige Spring beans om de reactive modus te activeren, vergelijkbaar met wat @EnableWebMvc doet bij Spring Web MVC.

Server

Nu de routes zijn gedefinieerd, hebben we een applicatieserver nodig. In Spring WebFlux kan je in de code een applicatieserver opstarten die de nodige routedefinities bevat, zonder dat er een Spring applicatiecontext aan te pas komt. Daarvoor vertrekken we van een routerFunction (zoals die van hierboven), die we transformeren naar een HttpHandler:

 

HttpHandler httpHandler = RouterFunctions.toHttpHandler(routes);

 

Een HttpHandler is een abstractie die in Spring 5 werd geïntroduceerd. Je kunt er een grote variatie aan reactive runtimes op inpluggen.

 

Vervolgens maken we een ReactorHttpHandlerAdapter om de handler te koppelen met de Reactor Netty runtime:

 

ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapterhttpHandler);

 

Nu moeten we enkel nog een server opstarten op basis van die adapter en we zijn klaar om HTTP requests te ontvangen.

 

Zo ziet de volledige code eruit:

 

public class MyWebApplication {

    public static void main(String[] args) {

        MyWebApplication app = new MyWebApplication();

        RouterFunction<ServerResponse> routes = app.getRoutes();

        HttpHandler httpHandler = RouterFunctions.toHttpHandler(routes);

        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);

        HttpServer server = HttpServer.create(“localhost”, 8080);

        server.startAndAwait(adapter);

    }

    public RouterFunction<ServerResponse> getRoutes() {

        return RouterFunctions.route(GET(“/person/{id}”),

                request ->

                        ServerResponse.ok()

                                .body(Mono.just(new Person(request.pathVariable(“id”))), Person.class));

    }

}

Conclusie

Deze blog fungeert als een eerste introductie tot Spring WebFlux. We toonden hoe je met de nieuwe functionele API aan de slag kunt gaan. Wil je meteen zelf van start gaan? Bekijk dan zeker ook even dit sample project: https://github.com/poutsma/web-function-sample

Auteur: Faros, 5 feb 2018