Spring 5 WebFlux functional programming model
Spring 5 has the new module WebFlux to create reactive web applications. Spring WebFlux supports two programming models: traditional annotation-based programming model and functional programming model. In this post, we'll going to scratch the surface of the new functional programming model. This post assumes that you already know concepts like Flux
and Mono
in Reactor.
We'll use a calculator service as the example. This service supports operations like add
, subtract
, divide
and multiply
with two operands. The parameter operator
specifies the operation to call, while parameters v1
and v2
specify the two operands. For example, the url /calculator?operator=add&v1=1&v2=2
invokes the add
operation with values 1
and 2
.
Basic setup
The easiest way to create a WebFlux project is using Spring Initializr. We create a Maven-based Java project with Spring Boot 2.0.0-M5 and add Reactive Web as the dependency.
Handler function
Handler functions are responsible for handling incoming HTTP requests. Each handler function takes a ServerRequest
and returns a Mono<ServerResponse>
. Multiple handler functions for the same service are typically organized in the same class.
In the CalculatorHandler
listed below, we have different methods add
, subtract
, multiply
and divide
to handle different requests. These methods use calculate
to parse the request and calculate the result.
@Component
public class CalculatorHandler {
public Mono<ServerResponse> add(final ServerRequest request) {
return calculate(request, (v1, v2) -> v1 + v2);
}
public Mono<ServerResponse> subtract(final ServerRequest request) {
return calculate(request, (v1, v2) -> v1 - v2);
}
public Mono<ServerResponse> multiply(final ServerRequest request) {
return calculate(request, (v1, v2) -> v1 * v2);
}
public Mono<ServerResponse> divide(final ServerRequest request) {
return calculate(request, (v1, v2) -> v1 / v2);
}
private Mono<ServerResponse> calculate(final ServerRequest request,
final BiFunction<Integer, Integer, Integer> calculateFunc) {
final Tuple2<Integer, Integer> operands = extractOperands(request);
return ServerResponse
.ok()
.body(Mono.just(calculateFunc.apply(operands.getT1(), operands.getT2())), Integer.class);
}
private Tuple2<Integer, Integer> extractOperands(final ServerRequest request) {
return Tuples.of(parseOperand(request, "v1"), parseOperand(request, "v2"));
}
private int parseOperand(final ServerRequest request, final String param) {
try {
return Integer.parseInt(request.queryParam(param).orElse("0"));
} catch (final NumberFormatException e) {
return 0;
}
}
}
Router function
Router functions are responsible for routing the incoming HTTP requests to the correct handler function. Each router function takes a ServerRequest
, and returns a Mono<HandlerFunction>
.
In the bean of type RouterFunction
created by routerFunction
, we only match the request path /calculator
. We extract the parameter operator
from the request and use reflections to find the correct handler function in CalculatorHandler
to handle the request. We can see that Reactor makes the error handling very easy to implement and understand.
@Configuration
public class Config {
@Bean
@Autowired
public RouterFunction<ServerResponse> routerFunction(final CalculatorHandler calculatorHandler) {
return RouterFunctions.route(RequestPredicates.path("/calculator"), request ->
request.queryParam("operator").map(operator ->
Mono.justOrEmpty(
ReflectionUtils.findMethod(CalculatorHandler.class, operator, ServerRequest.class))
.flatMap(method -> (Mono<ServerResponse>) ReflectionUtils
.invokeMethod(method, calculatorHandler, request))
.switchIfEmpty(ServerResponse.badRequest().build())
.onErrorResume(
ex -> ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).build()))
.orElse(ServerResponse.badRequest().build()));
}
}
Test
We can test this service using TestRestTemplate
provided by Spring Boot.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class FunctionalStarterApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void calculate() {
assertThat(doCalculate("add", "3", "5")).isEqualTo("8");
assertThat(doCalculate("subtract", "3", "5")).isEqualTo("-2");
assertThat(doCalculate("multiply", "3", "5")).isEqualTo("15");
assertThat(doCalculate("divide", "9", "3")).isEqualTo("3");
}
private String doCalculate(final String operator, final String operand1, final String operand2) {
return this.restTemplate.getForObject(
String.format("/calculator?operator=%s&v1=%s&v2=%s", operator, operand1, operand2),
String.class);
}
}
Source code
Full source code can be found on GitHub VividcodeIO/spring5-webflux-functional-starter.