Skip to content

Commit ff1f368

Browse files
committed
Improve Kotlin documentation
1 parent cd69a4a commit ff1f368

File tree

1 file changed

+170
-49
lines changed

1 file changed

+170
-49
lines changed

src/docs/asciidoc/languages/kotlin.adoc

Lines changed: 170 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -333,54 +333,6 @@ when you need to register routes depending on dynamic data (for example, from a
333333
See https://github.com/mixitconf/mixit/[MiXiT project] for a concrete example.
334334

335335

336-
337-
=== Coroutines
338-
339-
As of Spring Framework 5.2, https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] support
340-
is provided via:
341-
342-
* 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`
343-
* Suspending function support in Spring WebFlux annotated `@Controller`
344-
* 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.
345-
* WebFlux.fn {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/co-router.html[coRouter { }]
346-
347-
Coroutines extensions use `await` prefix or `AndAwait` suffix, and most are using similar
348-
names to their reactive counterparts.
349-
350-
[source,kotlin,indent=0]
351-
----
352-
@Configuration
353-
class RouterConfiguration {
354-
355-
@Bean
356-
fun mainRouter(userHandler: UserHandler) = coRouter {
357-
GET("/", userHandler::listView)
358-
GET("/api/user", userHandler::listApi)
359-
}
360-
}
361-
----
362-
363-
[source,kotlin,indent=0]
364-
----
365-
class UserHandler(builder: WebClient.Builder) {
366-
367-
private val client = builder.baseUrl("...").build()
368-
369-
suspend fun listView(request: ServerRequest): ServerResponse =
370-
ServerResponse.ok().renderAndAwait("users", mapOf("users" to
371-
client.get().uri("...").awaitExchange().awaitBody<User>()))
372-
373-
suspend fun listApi(request: ServerRequest): ServerResponse =
374-
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait(
375-
client.get().uri("...").awaitExchange().awaitBody<User>())
376-
}
377-
----
378-
379-
Read this blog post about https://medium.com/@elizarov/structured-concurrency-722d765aa952[structured concurrency]
380-
to understand how to run code concurrently with Coroutines.
381-
382-
383-
384336
=== MockMvc DSL
385337

386338
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:
440392
"""
441393
----
442394

443-
NOTE: Kotlin Script Templates are not compatible yet with Spring Boot fatjar mechanism, see related
395+
WARNING: Kotlin Script Templates support is experimental and not compatible yet with Spring Boot fatjar mechanism, see related
444396
https://youtrack.jetbrains.com/issue/KT-21443[KT-21443] and https://youtrack.jetbrains.com/issue/KT-27956[KT-27956]
445397
issues.
446398

447399
See the https://github.com/sdeleuze/kotlin-script-templating[kotlin-script-templating] example
448400
project for more details.
449401

402+
== Coroutines
403+
404+
Kotlin https://kotlinlang.org/docs/reference/coroutines-overview.html[Coroutines] are Kotlin
405+
lightweight threads allowing to write non-blocking code in an imperative way. On language side,
406+
suspending functions provides an abstraction for asynchronous operations while on library side
407+
https://github.com/Kotlin/kotlinx.coroutines[kotlinx.coroutines] provides functions likes
408+
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html[`async { }`]
409+
and types like https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html[`Flow`].
410+
411+
Spring Framework provides support for Coroutines on the following scope:
412+
413+
* 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`
414+
* Suspending function support in Spring WebFlux annotated `@Controller`
415+
* 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.
416+
* WebFlux.fn {doc-root}/spring-framework/docs/{spring-version}/kdoc-api/spring-framework/org.springframework.web.reactive.function.server/co-router.html[coRouter { }] DSL
417+
418+
=== How Reactive translates to Coroutines?
419+
420+
For return values, the translation from Reactive to Coroutines APIs is the following:
421+
422+
* `fun handler(): Mono<Void>` becomes `suspend fun handler()`
423+
* `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)
424+
* `fun handler(): Flux<T>` becomes `fun handler(): Flow<T>`
425+
426+
For input parameters:
427+
428+
* 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.
429+
* If laziness is needed, `fun handler(mono: Mono<T>)` becomes `fun handler(supplier: () -> T)` or `fun handler(supplier: () -> T?)`
430+
431+
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:
432+
433+
* `Flow` is push-based while `Flux` is push-pull hybrid
434+
* Backpressure is implemented via suspending functions
435+
* `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]
436+
* 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`
437+
* Collect operations are suspending functions
438+
* `map` operator supports asynchronous operation (no need for `flatMap`) since it takes a suspending function parameter
439+
440+
Read this blog post about https://medium.com/@elizarov/structured-concurrency-722d765aa952[structured concurrency]
441+
to understand how to run code concurrently with Coroutines and how are managed exceptions and cancellations.
442+
443+
=== Controllers
444+
445+
Here is an example of a Coroutines `@RestController`.
446+
447+
[source,kotlin,indent=0]
448+
----
449+
@RestController
450+
class CoroutinesRestController(client: WebClient, banner: Banner) {
451+
452+
@GetMapping("/suspend")
453+
suspend fun suspendingEndpoint(): Banner {
454+
delay(10)
455+
return banner
456+
}
457+
458+
@GetMapping("/flow")
459+
fun flowEndpoint() = flow {
460+
delay(10)
461+
emit(banner)
462+
delay(10)
463+
emit(banner)
464+
}
465+
466+
@GetMapping("/deferred")
467+
fun deferredEndpoint() = GlobalScope.async {
468+
delay(10)
469+
banner
470+
}
471+
472+
@GetMapping("/sequential")
473+
suspend fun sequential(): List<Banner> {
474+
val banner1 = client
475+
.get()
476+
.uri("/suspend")
477+
.accept(MediaType.APPLICATION_JSON)
478+
.awaitExchange()
479+
.awaitBody<Banner>()
480+
val banner2 = client
481+
.get()
482+
.uri("/suspend")
483+
.accept(MediaType.APPLICATION_JSON)
484+
.awaitExchange()
485+
.awaitBody<Banner>()
486+
return listOf(banner1, banner2)
487+
}
488+
489+
@GetMapping("/parallel")
490+
suspend fun parallel(): List<Banner> = coroutineScope {
491+
val deferredBanner1: Deferred<Banner> = async {
492+
client
493+
.get()
494+
.uri("/suspend")
495+
.accept(MediaType.APPLICATION_JSON)
496+
.awaitExchange()
497+
.awaitBody<Banner>()
498+
}
499+
val deferredBanner2: Deferred<Banner> = async {
500+
client
501+
.get()
502+
.uri("/suspend")
503+
.accept(MediaType.APPLICATION_JSON)
504+
.awaitExchange()
505+
.awaitBody<Banner>()
506+
}
507+
listOf(deferredBanner1.await(), deferredBanner2.await())
508+
}
509+
510+
@GetMapping("/error")
511+
suspend fun error(): ServerResponse {
512+
throw IllegalStateException()
513+
}
514+
515+
@GetMapping("/cancel")
516+
suspend fun cancel(): ServerResponse {
517+
throw CancellationException()
518+
}
519+
520+
}
521+
----
522+
523+
View rendering with a `@Controller` is also supported.
524+
525+
[source,kotlin,indent=0]
526+
----
527+
@Controller
528+
class CoroutinesViewController(banner: Banner) {
529+
530+
@GetMapping("/")
531+
suspend fun render(model: Model): String {
532+
delay(10)
533+
model["banner"] = banner
534+
return "index"
535+
}
536+
}
537+
----
538+
539+
=== WebFlux.fn
540+
541+
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.
542+
543+
[source,kotlin,indent=0]
544+
----
545+
@Configuration
546+
class RouterConfiguration {
547+
548+
@Bean
549+
fun mainRouter(userHandler: UserHandler) = coRouter {
550+
GET("/", userHandler::listView)
551+
GET("/api/user", userHandler::listApi)
552+
}
553+
}
554+
----
555+
556+
[source,kotlin,indent=0]
557+
----
558+
class UserHandler(builder: WebClient.Builder) {
559+
560+
private val client = builder.baseUrl("...").build()
561+
562+
suspend fun listView(request: ServerRequest): ServerResponse =
563+
ServerResponse.ok().renderAndAwait("users", mapOf("users" to
564+
client.get().uri("...").awaitExchange().awaitBody<User>()))
565+
566+
suspend fun listApi(request: ServerRequest): ServerResponse =
567+
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8).bodyAndAwait(
568+
client.get().uri("...").awaitExchange().awaitBody<User>())
569+
}
570+
----
450571

451572

452573

0 commit comments

Comments
 (0)