Spring Webflux com Kotlin

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.