Introdução à estrutura funcional da Web na primavera 5
1. Introdução
A estrutura Spring WebFlux apresenta uma nova estrutura funcional da Web construída usando princípios reativos.
Neste tutorial, aprenderemos como trabalhar com essa estrutura na prática.
Vamos basear isso em nosso tutorialGuide to Spring 5 WebFlux existente. Nesse guia, criamos um pequeno aplicativo REST reativo usando componentes baseados em anotações. Aqui, usaremos a estrutura funcional.
2. Dependência do Maven
Precisaremos da mesma dependênciaspring-boot-starter-webflux conforme definido no artigo anterior:
org.springframework.boot
spring-boot-starter-webflux
2.0.3.RELEASE
3. Estrutura funcional da Web
A estrutura funcional da Web apresenta um novo modelo de programação em que usamos funções para rotear e manipular solicitações.
Ao contrário do modelo baseado em anotações, onde usamos mapeamentos de anotações, aqui usaremosHandlerFunctioneRouterFunctions.
Além disso, a estrutura funcional da Web é construída na mesma pilha reativa na qual a estrutura reativa baseada em anotação foi construída.
3.1. HandlerFunction
OHandlerFunction representa uma função que gera respostas para solicitações encaminhadas a eles:
@FunctionalInterface
public interface HandlerFunction {
Mono handle(ServerRequest request);
}
Essa interface é principalmente umFunction<Request, Response<T>>, que se comporta de maneira muito semelhante a um servlet.
Embora, em comparação com um servlet padrãoServlet.service(ServletRequest req, ServletResponse res),HandlerFunction retorna a resposta em vez de tomá-la como um parâmetro que o torna livre de efeitos colaterais e mais fácil de testar e reutilizar.
3.2. RouterFunction
RouterFunction serve como uma alternativa para a anotação@RequestMapping. É usado para rotear solicitações de entrada para funções de manipulador:
@FunctionalInterface
public interface RouterFunction {
Mono> route(ServerRequest request);
// ...
}
Normalmente, podemos importarRouterFunctions.route(), uma função auxiliar para criar rotas, em vez de escrever uma função de roteador completa.
Ele nos permite rotear solicitações aplicando umRequestPredicate. Quando o predicado é correspondido, o segundo argumento, a função de manipulador, é retornado:
public static RouterFunction route(
RequestPredicate predicate,
HandlerFunction handlerFunction)
Ao retornar umRouterFunction,route() pode ser encadeado e aninhado para construir esquemas de roteamento complexos e poderosos.
4. Aplicativo REST reativo usando a Web funcional
Em nossoguide to Spring WebFlux tutorial, criamos um pequeno aplicativoEmployeeManagement REST usando@RestController areiaWebClient. anotado
Agora, vamos construir o mesmo aplicativo usando as funçõesRoutereHandler.
Para começar,we’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. Routes são registrados como beans Spring e podem ser criados dentro de qualquer classe que será usada como configuração Spring.
4.1. Recurso único
Vamos criar nossa primeira rota usandoRouterFunction t que publica um único recursoEmployee:
@Bean
RouterFunction getEmployeeByIdRoute() {
return route(GET("/employees/{id}"),
req -> ok().body(
employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));
}
Para dividi-lo, o primeiro argumento define uma solicitação HTTP GET que invocará ohandler., ou seja, retornaEmployee correspondente aemployeeRepository apenas se o caminho for/employee/{id} e a solicitação for do tipo GET.
4.2. Recurso de coleta
A seguir, para publicar o recurso de coleção, vamos adicionar outra rota:
@Bean
RouterFunction getAllEmployeesRoute() {
return route(GET("/employees"),
req -> ok().body(
employeeRepository().findAllEmployees(), Employee.class));
}
4.3. Atualização de recurso único
Por último, vamos adicionar uma rota para atualizarEmployee resource:
@Bean
RouterFunction updateEmployeeRoute() {
return route(POST("/employees/update"),
req -> req.body(toMono(Employee.class))
.doOnNext(employeeRepository()::updateEmployee)
.then(ok().build()));
}
5. Rotas de composição
Também podemos compor as rotas juntos em uma única função de roteador.
Vamos combinar nossas rotas criadas acima:
@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())));
}
Aqui, usamosRouterFunction.and() para combinar nossas rotas.
Finalmente, criamos todas as APIs REST usandoRoutereHandler que precisávamos para nosso aplicativoEmployeeManagement . Para executar nosso aplicativo, podemos usar rotas diferentes ou uma única composta que criamos acima.
6. Rotas de teste
Podemos usarWebTestClient para testar nossas rotas.
Para testar nossas rotas comWebTestClient we precisamos vincular nossas rotas usandobindToRouterFunction e construir nossa instância de cliente de teste.
Vamos testar nossogetEmployeeByIdRoute:
@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);
}
e da mesma formagetAllEmployeesRoute:
@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);
}
Também podemos testar nossoupdateEmployeeRoute afirmando que a situaçãoEmployee atualizada é atualizada 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);
}
Para obter mais detalhes sobre o teste comWebTestClient, consulte nosso tutorial emworking with WebClient and WebTestClient.
7. Sumário
Neste tutorial, apresentamos a nova estrutura funcional da web no Spring 5, examinamos suas duas funções principaisRouter andHandler e aprendemos como criar várias rotas para lidar com a solicitação e enviar a resposta.
Além disso, recriamos nossoEmployeeManagement application introduzido emGuide to Spring 5 WebFlux com nosso novo framework.
Estabelecendo sua base emReactor, a estrutura reativa brilharia totalmente com o acesso reativo a armazenamentos de dados. Infelizmente, a maioria dos armazenamentos de dados ainda não fornece esse acesso reativo, exceto para alguns bancos de dadosNoSQL, comoMongoDB.
Como sempre, o código-fonte completo pode ser encontradoover on Github.