Весенний Webflux с Kotlin

Весенний Webflux с Kotlin

1. обзор

В этом руководстве мы покажем, как использовать модуль Spring WebFlux с использованием языка программирования Kotlin.

Мы иллюстрируем, как использовать подходы на основе аннотаций и лямбда-стилей при определении конечных точек.

2. Spring WebFlux и Kotlin

Релиз Spring 5 представил две новые важные функции, среди которых нативная поддержка парадигмы реактивного программирования и возможность использовать язык программирования Kotlin.

В этом руководстве мы предполагаем, что мы уже настроили среду (обратитесь кone of our tutorials по этому вопросу) и понимаем синтаксис языка Kotlin (another tutorial по теме).

3. Аннотационный подход

WebFlux позволяет нам определять конечные точки, которые должны обрабатывать входящие запросы известным способом, используя аннотации инфраструктуры SpringMVC, такие как@RequestMapping или@PathVariable, или удобные аннотации, такие как@RestController или@GetMappingс.

Несмотря на то, что имена аннотаций совпадают,WebFlux’s делают методыnon-blocking.

Например, эта конечная точка:

@GetMapping(path = ["/numbers"],
  produces = [MediaType.APPLICATION_STREAM_JSON_VALUE])
@ResponseBody
fun getNumbers() = Flux.range(1, 100)

производит поток некоторых первых целых чисел. Если сервер работает наlocalhost:8080, то подключитесь к нему с помощью команды:

curl localhost:8080/stream

распечатает запрошенные номера.

4. Лямбда на основе подхода

Более новый подход в определении конечных точек заключается в использовании лямбда-выражений, которые присутствуют в Java начиная с версии 1.8. С помощью Kotlin лямбда-выражения можно использовать даже с более ранними версиями Java.

В WebFlux функции маршрутизатора - это функции, определяемыеRequestPredicate (другими словами, кто должен управлять запросом) иHandlerFunction (другими словами, как должен быть разработан запрос).

Функция-обработчик принимает экземплярServerRequest и создает экземплярMono<ServerResponse>.

В Котлине if the last function argument is a lambda expression, it can be placed outside the parentheses.

Такой синтаксис позволяет выделить разделение между предикатом запроса и функцией-обработчиком.

router {
    GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
}

для функций роутера.

Функция имеет понятный для человека формат: как только запрос типа GET поступит в / route, затем создаст ответ (игнорируя содержимое запроса - отсюда символ подчеркивания) с HTTP-статусом OK и с телом, созданным из данный объект.

Теперь, чтобы заставить его работать в WebFlux, мы должны поместить функцию router в класс:

@Configuration
class SimpleRoute {

    @Bean
    fun route() = router {
        GET("/route") { _ -> ServerResponse.ok().body(fromObject(arrayOf(1, 2, 3))) }
    }
}

Зачастую логика наших приложений требует, чтобы мы создавали более сложные функции маршрутизатора.

В WebFlux функция DSL маршрутизатора Kotlin определяет множество функций, таких какaccept,and,or,nest,invoke,GET,POST с помощьюextension functions, который позволяет нам создавать составные функции маршрутизатора:

router {
    accept(TEXT_HTML).nest {
        (GET("/device/") or GET("/devices/")).invoke(handler::getAllDevices)
    }
}

Переменнаяhandler должна быть экземпляром класса, реализующего методgetAllDevices() со стандартной сигнатуройHandlerFunction:

fun getAllDevices(request: ServerRequest): Mono

как мы уже упоминали выше.

Чтобы обеспечить надлежащее разделение задач, мы можем поместить определения несвязанных функций маршрутизатора в отдельные классы:

@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
}

Мы можем получить доступ к переменным пути с помощьюString-значного методаpathVariable():

val id = request.pathVariable("id")

а доступ к телуServerRequest достигается с помощью методовbodyToMono иbodyToFlux, то есть:

val device: Mono = request
  .bodyToMono(Device::class.java)

5. тестирование

Чтобы протестировать функции маршрутизатора, мы должны создать экземплярWebTestClient для маршрутизаторовSimpleRoute().route(), которые мы хотим протестировать:

var client = WebTestClient.bindToRouterFunction(SimpleRoute().route()).build()

Теперь мы готовы проверить, возвращает ли функция-обработчик маршрутизатора статус OK:

client.get()
  .uri("/route")
  .exchange()
  .expectStatus()
  .isOk

Интерфейс WebTestClient определяет методы, которые позволяют нам тестировать все методы HTTP-запроса, такие какGET,POST иPUT, даже без запуска сервера.

Чтобы проверить содержимое тела ответа, мы можем использовать методjson():

client.get()
  .uri("/route")
  .exchange()
  .expectBody()
  .json("[1, 2, 3]")

6. Заключение

В этой статье мы продемонстрировали, как использовать основные функции платформы WebFlux с помощью Kotlin.

Мы кратко упомянули хорошо известный подход, основанный на аннотациях, при определении конечных точек и посвятили больше времени, чтобы проиллюстрировать, как определить их с помощью функций маршрутизатора в стиле, основанном на лямбда-выражениях.

Все фрагменты кода можно найти в нашем репозиторииover on GitHub.