From ff1f3687518c92aea35df041167ea443dd77d136 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Tue, 9 Apr 2019 23:34:30 +0200 Subject: [PATCH] Improve Kotlin documentation --- src/docs/asciidoc/languages/kotlin.adoc | 219 ++++++++++++++++++------ 1 file changed, 170 insertions(+), 49 deletions(-) diff --git a/src/docs/asciidoc/languages/kotlin.adoc b/src/docs/asciidoc/languages/kotlin.adoc index 2b50b1a01c7..9d4ce2e398a 100644 --- a/src/docs/asciidoc/languages/kotlin.adoc +++ b/src/docs/asciidoc/languages/kotlin.adoc @@ -333,54 +333,6 @@ when you need to register routes depending on dynamic data (for example, from a See https://github.com/mixitconf/mixit/[MiXiT project] for a concrete example. - -=== Coroutines - -As of Spring Framework 5.2, https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] support -is provided via: - -* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html[Deferred] and https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[Flow] return value support in Spring WebFlux annotated `@Controller` -* Suspending function support in Spring WebFlux annotated `@Controller` -* Extensions for WebFlux {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.client/index.html[client] and {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/index.html[server] functional API. -* WebFlux.fn {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] - -Coroutines extensions use `await` prefix or `AndAwait` suffix, and most are using similar -names to their reactive counterparts. - -[source,kotlin,indent=0] ----- -@Configuration -class RouterConfiguration { - - @Bean - fun mainRouter(userHandler: UserHandler) = coRouter { - GET("/", userHandler::listView) - GET("/api/user", userHandler::listApi) - } -} ----- - -[source,kotlin,indent=0] ----- -class UserHandler(builder: WebClient.Builder) { - - private val client = builder.baseUrl("...").build() - - suspend fun listView(request: ServerRequest): ServerResponse = - ServerResponse.ok().renderAndAwait("users", mapOf("users" to - client.get().uri("...").awaitExchange().awaitBody())) - - suspend fun listApi(request: ServerRequest): ServerResponse = - ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait( - client.get().uri("...").awaitExchange().awaitBody()) -} ----- - -Read this blog post about https://medium.com/@elizarov/structured-concurrency-722d765aa952[structured concurrency] -to understand how to run code concurrently with Coroutines. - - - === MockMvc DSL A Kotlin DSL is provided via `MockMvc` Kotlin extensions in order to provide a more @@ -440,13 +392,182 @@ refactoring support in a supported IDE, as the following example shows: """ ---- -NOTE: Kotlin Script Templates are not compatible yet with Spring Boot fatjar mechanism, see related +WARNING: Kotlin Script Templates support is experimental and not compatible yet with Spring Boot fatjar mechanism, see related https://youtrack.jetbrains.com/issue/KT-21443[KT-21443] and https://youtrack.jetbrains.com/issue/KT-27956[KT-27956] issues. See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example project for more details. +== Coroutines + +Kotlin https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] are Kotlin +lightweight threads allowing to write non-blocking code in an imperative way. On language side, +suspending functions provides an abstraction for asynchronous operations while on library side +https://github.com/Kotlin/kotlinx.coroutines[kotlinx.coroutines] provides functions likes +https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`] +and types like https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[`Flow`]. + +Spring Framework provides support for Coroutines on the following scope: + +* https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html[Deferred] and https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[Flow] return values support in Spring WebFlux annotated `@Controller` +* Suspending function support in Spring WebFlux annotated `@Controller` +* Extensions for WebFlux {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.client/index.html[client] and {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/index.html[server] functional API. +* WebFlux.fn {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL + +=== How Reactive translates to Coroutines? + +For return values, the translation from Reactive to Coroutines APIs is the following: + +* `fun handler(): Mono` becomes `suspend fun handler()` +* `fun handler(): Mono` becomes `suspend fun handler(): T` or `suspend fun handler(): T?` depending on if the `Mono` can be empty or not (with the advantage of beeing more statically typed) +* `fun handler(): Flux` becomes `fun handler(): Flow` + +For input parameters: + +* If laziness is not needed, `fun handler(mono: Mono)` becomes `fun handler(value: T)` since a suspending functions can be invoked to get the value parameter. +* If laziness is needed, `fun handler(mono: Mono)` becomes `fun handler(supplier: () -> T)` or `fun handler(supplier: () -> T?)` + +https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[`Flow`] is `Flux` equivalent in Coroutines world, suitable for hot or cold stream, finite or infinite streams, with the following main differences: + +* `Flow` is push-based while `Flux` is push-pull hybrid +* Backpressure is implemented via suspending functions +* `Flow` has only a https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/collect.html[single suspending `collect` method] and operators are implemented as https://kotlinlang.org/docs/reference/extensions.html[extensions] +* https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-core/common/src/flow/operators[Operators are easy to implement] thanks to Coroutines and extensions allow to add custom ones easily to `Flow` +* Collect operations are suspending functions +* `map` operator supports asynchronous operation (no need for `flatMap`) since it takes a suspending function parameter + +Read this blog post about https://medium.com/@elizarov/structured-concurrency-722d765aa952[structured concurrency] +to understand how to run code concurrently with Coroutines and how are managed exceptions and cancellations. + +=== Controllers + +Here is an example of a Coroutines `@RestController`. + +[source,kotlin,indent=0] +---- +@RestController +class CoroutinesRestController(client: WebClient, banner: Banner) { + + @GetMapping("/suspend") + suspend fun suspendingEndpoint(): Banner { + delay(10) + return banner + } + + @GetMapping("/flow") + fun flowEndpoint() = flow { + delay(10) + emit(banner) + delay(10) + emit(banner) + } + + @GetMapping("/deferred") + fun deferredEndpoint() = GlobalScope.async { + delay(10) + banner + } + + @GetMapping("/sequential") + suspend fun sequential(): List { + val banner1 = client + .get() + .uri("/suspend") + .accept(MediaType.APPLICATION_JSON) + .awaitExchange() + .awaitBody() + val banner2 = client + .get() + .uri("/suspend") + .accept(MediaType.APPLICATION_JSON) + .awaitExchange() + .awaitBody() + return listOf(banner1, banner2) + } + + @GetMapping("/parallel") + suspend fun parallel(): List = coroutineScope { + val deferredBanner1: Deferred = async { + client + .get() + .uri("/suspend") + .accept(MediaType.APPLICATION_JSON) + .awaitExchange() + .awaitBody() + } + val deferredBanner2: Deferred = async { + client + .get() + .uri("/suspend") + .accept(MediaType.APPLICATION_JSON) + .awaitExchange() + .awaitBody() + } + listOf(deferredBanner1.await(), deferredBanner2.await()) + } + + @GetMapping("/error") + suspend fun error(): ServerResponse { + throw IllegalStateException() + } + + @GetMapping("/cancel") + suspend fun cancel(): ServerResponse { + throw CancellationException() + } + +} +---- + +View rendering with a `@Controller` is also supported. + +[source,kotlin,indent=0] +---- +@Controller +class CoroutinesViewController(banner: Banner) { + + @GetMapping("/") + suspend fun render(model: Model): String { + delay(10) + model["banner"] = banner + return "index" + } +} +---- + +=== WebFlux.fn + +Here is an example of Coroutines router definined via the {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL and related handlers. + +[source,kotlin,indent=0] +---- +@Configuration +class RouterConfiguration { + + @Bean + fun mainRouter(userHandler: UserHandler) = coRouter { + GET("/", userHandler::listView) + GET("/api/user", userHandler::listApi) + } +} +---- + +[source,kotlin,indent=0] +---- +class UserHandler(builder: WebClient.Builder) { + + private val client = builder.baseUrl("...").build() + + suspend fun listView(request: ServerRequest): ServerResponse = + ServerResponse.ok().renderAndAwait("users", mapOf("users" to + client.get().uri("...").awaitExchange().awaitBody())) + + suspend fun listApi(request: ServerRequest): ServerResponse = + ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait( + client.get().uri("...").awaitExchange().awaitBody()) +} +----