Spring Webflux com Kotlin
1. Visão geral
Neste tutorial, demonstramos como usar o módulo Spring WebFlux usando a linguagem de programação Kotlin.
Ilustramos como usar as abordagens de estilo baseada em anotação e baseada em lambda na definição dos pontos de extremidade.
2. Spring WebFlux e Kotlin
O lançamento do Spring 5 introduziu dois novos grandes recursos, entre os quais o suporte nativo ao paradigma de programação reativa e a possibilidade de usar a linguagem de programação Kotlin.
Ao longo deste tutorial, presumimos que já configuramos o ambiente (consulteone of our tutorials sobre este problema) e entendemos a sintaxe da linguagem Kotlin (another tutorial sobre o tópico).
3. Abordagem baseada em anotação
O WebFlux nos permite definir os endpoints que devem lidar com as solicitações de entrada de maneira bem conhecida, usando as anotações do framework SpringMVC como@RequestMapping ou@PathVariable ou anotações de conveniência como@RestController ou@GetMapping.
Apesar de os nomes das anotações serem iguais, osWebFlux’s fazem os métodos seremnon-blocking.
Por exemplo, este terminal:
@GetMapping(path = ["/numbers"],
produces = [MediaType.APPLICATION_STREAM_JSON_VALUE])
@ResponseBody
fun getNumbers() = Flux.range(1, 100)
produz um fluxo de alguns primeiros inteiros. Se o servidor for executado emlocalhost:8080, conecte-se a ele por meio do comando:
curl localhost:8080/stream
imprimirá os números solicitados.
4. Abordagem baseada em Lambda
Uma abordagem mais nova na definição dos pontos de extremidade é por meio de expressões lambda que estão presentes em Java desde a versão 1.8. Com a ajuda do Kotlin, as expressões lambda podem ser usadas mesmo em versões anteriores do Java.
No WebFlux, as funções do roteador são funções determinadas porRequestPredicate (ou seja, quem deve gerenciar a solicitação) eHandlerFunction (ou seja, como a solicitação deve ser elaborada).
Uma função de tratamento aceita uma instânciaServerRequest e produz umaMono<ServerResponse>.
Em Kotlin, if the last function argument is a lambda expression, it can be placed outside the parentheses.
Essa sintaxe nos permite destacar a divisão entre o predicado de solicitação e a função de manipulador
router {
GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
}
para funções do roteador.
A função tem um formato legível por humanos: quando uma solicitação do tipo GET chega em / route, constrói uma resposta (ignorando o conteúdo da solicitação - daí o símbolo sublinhado) com o status HTTP OK e com um corpo construído a partir do determinado objeto.
Agora, para fazê-lo funcionar no WebFlux, devemos colocar a função de roteador em uma classe:
@Configuration
class SimpleRoute {
@Bean
fun route() = router {
GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
}
}
Freqüentemente, a lógica de nossos aplicativos exige que construamos funções de roteador mais sofisticadas.
No WebFlux, a função de roteador DSL de Kotlin define uma variedade de funções comoaccept,and,or,nest,invoke,GET,POST por meio deextension functions que nos permite construir funções de roteador compostas:
router {
accept(TEXT_HTML).nest {
(GET("/device/") or GET("/devices/")).invoke(handler::getAllDevices)
}
}
A variávelhandler deve ser uma instância de uma classe implementando um métodogetAllDevices() com a assinatura padrãoHandlerFunction:
fun getAllDevices(request: ServerRequest): Mono
como mencionamos acima.
Para manter uma separação adequada de preocupações, podemos colocar as definições de funções de roteador não relacionadas em classes separadas:
@Configuration
class HomeSensorsRouters(private val handler: HomeSensorsHandler) {
@Bean
fun roomsRouter() = router {
(accept(TEXT_HTML) and "/room").nest {
GET("/light", handler::getLightReading)
POST("/light", handler::setLight)
}
}
// eventual other router function definitions
}
Podemos acessar as variáveis de caminho por meio do métodoString-valorpathVariable():
val id = request.pathVariable("id")
enquanto o acesso ao corpo deServerRequest é obtido por meio dos métodosbodyToMonoebodyToFlux, ou seja:
val device: Mono = request
.bodyToMono(Device::class.java)
5. Teste
Para testar as funções do roteador, devemos construir uma instânciaWebTestClient dos roteadoresSimpleRoute().route() que queremos testar:
var client = WebTestClient.bindToRouterFunction(SimpleRoute().route()).build()
Agora estamos prontos para testar se a função de manipulador do roteador retorna o status OK:
client.get()
.uri("/route")
.exchange()
.expectStatus()
.isOk
A interface WebTestClient define os métodos que nos permitem testar todos os métodos de solicitação HTTP comoGET,POSTePUT mesmo sem executar o servidor.
Para testar o conteúdo do corpo da resposta, podemos usar o métodojson():
client.get()
.uri("/route")
.exchange()
.expectBody()
.json("[1, 2, 3]")
6. Conclusão
Neste artigo, demonstramos como usar os recursos básicos da estrutura WebFlux usando o Kotlin.
Mencionamos brevemente a conhecida abordagem baseada em anotação na definição dos pontos finais e dedicamos mais tempo para ilustrar como defini-los usando funções de roteador em uma abordagem baseada no estilo lambda.
Todos os trechos de código podem ser encontrados em nosso repositórioover on GitHub.