Improve Kotlin documentation
This commit is contained in:
parent
cd69a4a03b
commit
ff1f368751
|
@ -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<User>()))
|
||||
|
||||
suspend fun listApi(request: ServerRequest): ServerResponse =
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait(
|
||||
client.get().uri("...").awaitExchange().awaitBody<User>())
|
||||
}
|
||||
----
|
||||
|
||||
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<Void>` becomes `suspend fun handler()`
|
||||
* `fun handler(): Mono<T>` 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<T>` becomes `fun handler(): Flow<T>`
|
||||
|
||||
For input parameters:
|
||||
|
||||
* If laziness is not needed, `fun handler(mono: Mono<T>)` 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<T>)` 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<Banner> {
|
||||
val banner1 = client
|
||||
.get()
|
||||
.uri("/suspend")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.awaitExchange()
|
||||
.awaitBody<Banner>()
|
||||
val banner2 = client
|
||||
.get()
|
||||
.uri("/suspend")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.awaitExchange()
|
||||
.awaitBody<Banner>()
|
||||
return listOf(banner1, banner2)
|
||||
}
|
||||
|
||||
@GetMapping("/parallel")
|
||||
suspend fun parallel(): List<Banner> = coroutineScope {
|
||||
val deferredBanner1: Deferred<Banner> = async {
|
||||
client
|
||||
.get()
|
||||
.uri("/suspend")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.awaitExchange()
|
||||
.awaitBody<Banner>()
|
||||
}
|
||||
val deferredBanner2: Deferred<Banner> = async {
|
||||
client
|
||||
.get()
|
||||
.uri("/suspend")
|
||||
.accept(MediaType.APPLICATION_JSON)
|
||||
.awaitExchange()
|
||||
.awaitBody<Banner>()
|
||||
}
|
||||
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<User>()))
|
||||
|
||||
suspend fun listApi(request: ServerRequest): ServerResponse =
|
||||
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait(
|
||||
client.get().uri("...").awaitExchange().awaitBody<User>())
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue