$30 off During Our Annual Pro Sale. View Details »

Spring-Flavored Kotlin Coroutines

Spring-Flavored Kotlin Coroutines

Kotlin has built-in support for asynchronous programming with Kotlin Coroutines that are designed to allow for simple and easy-to-understand code. We'll witness that reduction in complexity that coroutines bring and dissect what kind of magic is working behind the scenes to make it possible, how it all integrates with Spring and how can you integrate coroutines with any kind of asynchronous code you write.

Roman Elizarov

October 08, 2019
Tweet

More Decks by Roman Elizarov

Other Decks in Programming

Transcript

  1. Spring-Flavored
    Kotlin Coroutines
    Roman Elizarov
    October 7–10, 2019
    Austin Convention Center

    View Slide

  2. Speaker: Roman Elizarov
    Professional developer since 2000
    Previously developed high-perf trading software
    @ Devexperts
    Teach concurrent & distributed programming
    @ St. Petersburg ITMO University
    Chief judge
    @ Northern Eurasia Contest / ICPC
    Now team lead in Kotlin Libraries
    @ JetBrains
    elizarov @
    relizarov

    View Slide

  3. Kotlin – Programming Language for

    View Slide

  4. Kotlin – Programming Language for

    View Slide

  5. Backend evolution
    Starting with “good old days”

    View Slide

  6. ET 1
    ET 2
    ET N

    DB
    Old-school client-server monolith
    Clients
    Executor Threads

    View Slide

  7. ET 1
    ET 2
    ET N

    DB
    Incoming request
    Clients
    Executor Threads

    View Slide

  8. ET 1
    ET 2
    ET N

    DB
    Blocks tread

    Clients
    Executor Threads

    View Slide

  9. ET 1
    ET 2
    ET N

    DB
    Sizing threads – easy
    Clients
    N = number of DB
    connections
    Executor Threads

    View Slide

  10. Services

    View Slide

  11. ET 1
    ET 2
    ET N

    DB
    Old-school client-server monolith
    Clients
    Executor Threads

    View Slide

  12. ET 1
    ET 2
    ET N

    DB
    Now with Services
    Service
    Clients
    Executor Threads

    View Slide

  13. ET 1
    ET 2
    ET N

    Services everywhere

    Service K
    Service 1
    Service 2
    Clients
    Executor Threads

    View Slide

  14. Complex business logic
    fun placeOrder(order: Order): Response {

    }

    View Slide

  15. Complex business logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)

    }

    View Slide

  16. Complex business logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount)

    }

    View Slide

  17. Complex business logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }

    }

    View Slide

  18. Complex business logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }

    }

    View Slide

  19. Complex business logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }

    View Slide

  20. Complex business logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }

    View Slide

  21. What if a service is slow?
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }

    View Slide

  22. Slow service
    @Service
    class MarginService {
    private val rest = RestTemplate()
    fun loadMargin(account: Account): Margin =
    rest.getForObject(
    "http://localhost:9090/margin/${account.id}",
    Margin::class.java)!!
    }

    View Slide

  23. Slow service
    @Service
    class MarginService {
    private val rest = RestTemplate()
    fun loadMargin(account: Account): Margin =
    rest.getForObject(
    "http://localhost:9090/margin/${account.id}",
    Margin::class.java)!!
    }

    View Slide

  24. Demo
    Slow service, blocking

    View Slide

  25. ET 2
    ET N

    Clients
    Blocks threads

    Service K
    Service 1
    Service 2
    ET 1

    Executor Threads

    View Slide

  26. ET 2
    ET N

    Clients
    Blocks threads

    Service K
    Service 1
    Service 2
    ET 1


    Executor Threads

    View Slide

  27. ET 2
    ET N

    Clients
    Blocks threads

    Service K
    Service 1
    Service 2
    ET 1



    Executor Threads

    View Slide

  28. Code that waits

    View Slide

  29. Asynchronous programming
    Writing code that waits

    View Slide

  30. ET 2
    ET N

    Clients
    Instead of blocking…

    Service K
    Service 1
    Service 2
    ET 1

    Executor Threads

    View Slide

  31. ET 2
    ET N

    Release the thread

    Service K
    Service 1
    Service 2
    ET 1

    Clients
    Executor Threads

    View Slide

  32. Clients
    ET 2
    ET N

    Resume operation later

    Service K
    Service 1
    Service 2
    ET 1

    Executor Threads

    View Slide

  33. But how?
    fun loadMargin(account: Account): Margin

    View Slide

  34. But how?
    Callbacks
    fun loadMargin(account: Account, callback: (Margin) -> Unit)

    View Slide

  35. But how?
    Callbacks
    Futures/Promises
    fun loadMargin(account: Account): CompletableFuture

    View Slide

  36. But how?
    Callbacks
    Futures/Promises/Reactive
    fun loadMargin(account: Account): Mono

    View Slide

  37. Reactive

    View Slide

  38. The logic
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }

    View Slide

  39. Going reactive
    fun placeOrder(order: Order): Mono

    View Slide

  40. Going reactive
    fun placeOrder(order: Order): Mono =

    View Slide

  41. Going reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)

    View Slide

  42. Going reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->

    }

    View Slide

  43. Going reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->
    if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    Mono.just(defaultMargin)
    }
    }

    View Slide

  44. Going reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->
    if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    Mono.just(defaultMargin)
    }
    }

    View Slide

  45. Going reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->
    if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    Mono.just(defaultMargin)
    }
    }
    .map { margin ->

    }

    View Slide

  46. Going reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->
    if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    Mono.just(defaultMargin)
    }
    }
    .map { margin ->
    validateOrder(order, margin)
    }

    View Slide

  47. Reactive Service
    @Service
    class MarginService {
    private val client = WebClient.create("http://localhost:9090/")
    fun loadMargin(account: Account): Mono =
    client
    .get()
    .uri("margin/${account.id}")
    .retrieve()
    .bodyToMono(Margin::class.java)
    }

    View Slide

  48. Reactive Service
    @Service
    class MarginService {
    private val client = WebClient.create("http://localhost:9090/")
    fun loadMargin(account: Account): Mono =
    client
    .get()
    .uri("margin/${account.id}")
    .retrieve()
    .bodyToMono(Margin::class.java)
    }

    View Slide

  49. Demo: Reactive
    Was it worth it?

    View Slide

  50. Choice 1: Direct code, blocking
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }

    View Slide

  51. Choice 2: Complex code, non-blocking
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->
    if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    Mono.just(defaultMargin)
    }
    }
    .map { margin ->
    validateOrder(order, margin)
    }

    View Slide

  52. Why not both?

    View Slide

  53. But how?
    Callbacks
    Futures/Promises/Reactive
    fun loadMargin(account: Account): Mono

    View Slide

  54. But how?
    Callbacks
    Futures/Promises/Reactive
    Kotlin Coroutines
    suspend fun loadMargin(account: Account): Margin

    View Slide

  55. Direct code: Blocking
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }

    View Slide

  56. Direct code: Coroutines
    suspend fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }
    Write regular code!
    Call suspending funs

    View Slide

  57. Coroutines Service
    @Service
    class MarginService {
    private val client = WebClient.create("http://localhost:9090/")
    suspend fun loadMargin(account: Account): Margin =
    client
    .get()
    .uri("margin/${account.id}")
    .retrieve()
    .awaitBody()
    }

    View Slide

  58. Coroutines Service
    @Service
    class MarginService {
    private val client = WebClient.create("http://localhost:9090/")
    suspend fun loadMargin(account: Account): Margin =
    client
    .get()
    .uri("margin/${account.id}")
    .retrieve()
    .awaitBody()
    }

    View Slide

  59. Demo: Coroutines

    View Slide

  60. View Slide

  61. Suspend behind the scenes
    suspend fun loadMargin(account: Account): Margin

    View Slide

  62. Suspend behind the scenes
    suspend fun loadMargin(account: Account): Margin
    fun loadMargin(account: Account, cont: Continuation)
    But why callback and not future/mono?

    View Slide

  63. Performance!
    Callback is a simple, low-level primitive
    Integration with async IO libraries is easy

    View Slide

  64. Service integration
    suspend fun loadMargin(account: Account): Margin

    View Slide

  65. Service integration
    suspend fun loadMargin(account: Account): Margin =
    suspendCoroutine { cont ->
    // install callback & use cont to resume
    }

    View Slide

  66. Server integrated with coroutines
    @PostMapping("/order")
    suspend fun placeOrder(order: Order): Response {

    }

    View Slide

  67. Server not integrated with coroutines
    @PostMapping("/order")
    fun placeOrder(order: Order): Mono = mono {

    }
    Coroutine builder

    View Slide

  68. Blocking integration
    @Component
    class Initializer {
    @EventListener(ApplicationStartedEvent::class)
    fun initData() {

    }
    }

    View Slide

  69. Blocking integration
    @Component
    class Initializer {
    @EventListener(ApplicationStartedEvent::class)
    fun initData() = runBlocking {

    }
    }
    Coroutine builder

    View Slide

  70. Suspend is efficient

    View Slide

  71. Suspend is efficient
    suspend fun placeOrder(order: Order): Response {
    val account = accountService.loadAccout(order.accountId)
    val margin = if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    defaultMargin
    }
    return validateOrder(order, margin)
    }
    One object allocated

    View Slide

  72. Reactive
    fun placeOrder(order: Order): Mono =
    accountService.loadAccount(order.accountId)
    .flatMap { account ->
    if (account.isOptionsAccount) {
    marginService.loadMargin(account)
    } else {
    Mono.just(defaultMargin)
    }
    }
    .map { margin ->
    validateOrder(order, margin)
    }
    Lambda allocated*
    Mono allocated
    Lambda allocated
    Mono allocated

    View Slide

  73. Let’s go deeper
    fun placeOrder(params: Params): Mono {
    // check pre-conditions
    return actuallyPlaceOrder(order)
    }
    fun actuallyPlaceOrder(order: Order): Mono

    View Slide

  74. Let’s go deeper (with coroutines)
    suspend fun placeOrder(params: Params): Response {
    // check pre-conditions
    return actuallyPlaceOrder(order)
    }
    suspend fun actuallyPlaceOrder(params: Params): Response
    Tail call optimization
    Tail call

    View Slide

  75. Call stack with coroutines
    Coroutine Builder
    placeOrder
    actuallyPlaceOrder
    moreLogic
    marginService.loadMargin
    suspendCoroutine

    View Slide

  76. Call stack with coroutines
    Coroutine Builder
    placeOrder
    actuallyPlaceOrder
    moreLogic
    marginService.loadMargin
    suspendCoroutine
    unwind
    Continuation in heap

    View Slide

  77. Learn more
    KotlinConf San Francisco 2017 GOTO Copenhagen 2018

    View Slide

  78. Scaling with coroutines
    With thread pools

    View Slide

  79. ET 2
    ET N

    Clients
    Thread pools
    ET 1
    Executor Threads

    View Slide

  80. ET 2
    ET N

    Clients
    Thread pools
    ET 1
    ST 2
    ST M1

    S1 1
    N = number of CPU cores M1
    = depends
    Service 1 Threads
    Executor Threads

    View Slide

  81. IO-bound (blocking)
    fun loadAccount(order: Order): Account {
    // some blocking code here....
    }

    View Slide

  82. IO-bound
    suspend fun loadAccount(order: Order): Account {
    // some blocking code here....
    }

    View Slide

  83. IO-bound withContext
    suspend fun loadAccount(order: Order): Account =
    withContext(dispatcher) {
    // some blocking code here....
    }

    View Slide

  84. IO-bound withContext
    suspend fun loadAccount(order: Order): Account =
    withContext(dispatcher) {
    // some blocking code here....
    }
    val dispatcher =
    Executors.newFixedThreadPool(M2).asCoroutineDispatcher()

    View Slide

  85. CPU-bound code
    fun validateOrder(order: Order, margin: Margin): Response {
    // perform CPU-consuming computation
    }

    View Slide

  86. CPU-bound code
    suspend fun validateOrder(order: Order, margin: Margin): Response =
    withContext(compute) {
    // perform CPU-consuming computation
    }
    val compute =
    Executors.newFixedThreadPool(M3).asCoroutineDispatcher()

    View Slide

  87. ET 2
    ET N

    Clients
    Fine-grained control and encapsulation
    ET 1 S1 1
    ST M1
    Service 2 Threads
    S1 1
    ST M2
    Service 3 Threads
    S1 1
    ST M3
    Async
    IO-bound
    CPU-
    bound
    Never blocked
    Service 1 Threads
    Executor Threads

    View Slide

  88. But there’s more!

    View Slide

  89. Cancellation

    View Slide

  90. withTimeout
    suspend fun placeOrder(order: Order): Response =
    withTimeout(1000) {
    // code before
    loadMargin(account)
    // code after
    }

    View Slide

  91. withTimeout propagation
    suspend fun placeOrder(order: Order): Response =
    withTimeout(1000) {
    // code before
    loadMargin(account)
    // code after
    }
    suspend fun loadMargin(account: Account): Margin =
    suspendCoroutine { cont ->
    // install callback & use cont to resume
    }

    View Slide

  92. withTimeout propagation
    suspend fun placeOrder(order: Order): Response =
    withTimeout(1000) {
    // code before
    loadMargin(account)
    // code after
    }
    suspend fun loadMargin(account: Account): Margin =
    suspendCancellableCoroutine { cont ->
    // install callback & use cont to resume
    }

    View Slide

  93. withTimeout propagation
    suspend fun placeOrder(order: Order): Response =
    withTimeout(1000) {
    // code before
    loadMargin(account)
    // code after
    }
    suspend fun loadMargin(account: Account): Margin =
    suspendCancellableCoroutine { cont ->
    // install callback & use cont to resume
    cont.invokeOnCancellation { … }
    }

    View Slide

  94. Concurrency
    Multiple things at the same time

    View Slide

  95. Example
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccount(order)
    val margin = marginService.loadMargin(order)
    return validateOrder(order, account, margin)
    }

    View Slide

  96. Example
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccount(order)
    val margin = marginService.loadMargin(order)
    return validateOrder(order, account, margin)
    }
    No data dependencies

    View Slide

  97. Concurrency with async (futures)
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccountAsync(order)
    val margin = marginService.loadMarginAsync(order)
    return validateOrder(order, account.await(), margin.await())
    }

    View Slide

  98. Concurrency with async (futures)
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccountAsync(order)
    val margin = marginService.loadMarginAsync(order)
    return validateOrder(order, account.await(), margin.await())
    }

    View Slide

  99. Concurrency with async (futures)
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccountAsync(order)
    val margin = marginService.loadMarginAsync(order)
    return validateOrder(order, account.await(), margin.await())
    }
    Fails?

    View Slide

  100. Concurrency with async (futures)
    fun placeOrder(order: Order): Response {
    val account = accountService.loadAccountAsync(order)
    val margin = marginService.loadMarginAsync(order)
    return validateOrder(order, account.await(), margin.await())
    }
    Fails?
    Leaks
    !

    View Slide

  101. Structured concurrency

    View Slide

  102. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }

    View Slide

  103. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }

    View Slide

  104. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }

    View Slide

  105. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }
    Fails?

    View Slide

  106. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }
    Fails?
    Cancels

    View Slide

  107. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }
    Fails?
    Cancels
    Cancels

    View Slide

  108. Concurrency with coroutines
    suspend fun placeOrder(order: Order): Response =
    coroutineScope {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    validateOrder(order, account.await(), margin.await())
    }
    Waits for completion of all children

    View Slide

  109. Enforcing structure

    View Slide

  110. Without coroutine scope?
    suspend fun placeOrder(order: Order): Response {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    return validateOrder(order, account.await(), margin.await())
    }

    View Slide

  111. Without coroutine scope?
    suspend fun placeOrder(order: Order): Response {
    val account = async { accountService.loadAccount(order) }
    val margin = async { marginService.loadMargin(order) }
    return validateOrder(order, account.await(), margin.await())
    }
    ERROR: Unresolved
    reference.

    View Slide

  112. Extensions of CoroutineScope
    fun CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
    ): Deferred

    View Slide

  113. Convention
    fun CoroutineScope.bg(params: Params) = launch {
    // …
    }
    Launches new
    coroutine

    View Slide

  114. Types as documentation
    fun foo(params: Params): Response
    suspend fun foo(params: Params): Response
    fun CoroutineScope.foo(params: Params): Response
    Fast, local
    Remote, or slow
    Side effect - bg

    View Slide

  115. Types are enforced
    fun foo(params: Params): Response
    suspend fun foo(params: Params): Response
    fun CoroutineScope.foo(params: Params): Response
    Not
    allowed
    But must
    provide scope
    explicitly
    Using coroutineScope { … }
    Fast, local
    Remote, or slow
    Side effect - bg

    View Slide

  116. Green threads / fibers
    Alternative way to async

    View Slide

  117. Green threads / Fibers
    ET 2
    ET N

    ET 1
    F 2
    F M

    Fibers
    F 1
    ~ Coroutines Hidden from developer
    Executor Threads

    View Slide

  118. But marking with suspend
    pays off at scale
    Fibers – everything suspending

    View Slide

  119. Thread switching
    And how to avoid it

    View Slide

  120. ET 2
    ET N

    Clients
    Threads
    ET 1 S1 1
    ST M1
    S1 1
    ST M2
    S1 1
    ST M3
    Service 2 Threads
    Service 3 Threads
    Service 1 Threads
    Executor Threads

    View Slide

  121. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1
    Executor Threads

    View Slide

  122. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1
    Executor Threads

    View Slide

  123. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1 ET N+1
    Executor Threads

    View Slide

  124. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1 ET N+1
    ET N+2
    Executor Threads

    View Slide

  125. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1 ET N+1


    ET M
    ET N+2
    ET N+M
    Executor Threads

    View Slide

  126. withContext for IO
    suspend fun loadAccount(order: Order): Account =
    withContext(dispatcher) {
    // some blocking code here....
    }
    val dispatcher =
    Executors.newFixedThreadPool(M2).asCoroutineDispatcher()

    View Slide

  127. withContext for Dispatсhers.IO
    suspend fun loadAccount(order: Order): Account =
    withContext(Dispatchers.IO) {
    // some blocking code here....
    }
    No thread switch from
    Dispatchers.Default pool

    View Slide

  128. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1
    Dispatchers.Default
    Executor Threads

    View Slide

  129. ET 2
    ET N

    Clients
    Solution – shared thread pool
    ET 1 ET N+1


    ET M
    ET N+2
    ET N+M
    Dispatchers.Default
    Dispatchers.IO
    Executor Threads

    View Slide

  130. Coroutines and data streams

    View Slide

  131. Returning many responses
    suspend fun foo(params: Params): Response One response
    suspend fun foo(params: Params): List Many responses

    View Slide

  132. Returning many responses
    suspend fun foo(params: Params): Response One response
    suspend fun foo(params: Params): List Many responses
    fun foo(params: Params): Flow Many responses
    async

    View Slide

  133. Kotlin Flows
    Reactive streams for coroutines

    View Slide

  134. fun foo(): Flow
    ~ Reactive Publisher / Flux

    View Slide

  135. fun foo(): Flow = flow {

    }

    View Slide

  136. fun foo(): Flow = flow {
    for (i in 1..10) {
    emit(i)
    delay(100)
    }
    }

    View Slide

  137. fun foo(): Flow = flow {
    for (i in 1..10) {
    emit(i)
    delay(100)
    }
    }
    suspend fun main() {
    foo().collect { x ->
    println(x)
    }
    }

    View Slide

  138. fun foo(): Flow = flow {
    for (i in 1..10) {
    emit(i)
    delay(100)
    }
    }
    suspend fun main() {
    foo()
    }
    Flow is cold: describes the data,
    does not run it until collected

    View Slide

  139. Flow operators
    fun foo(): Flow = flow {
    for (i in 1..10) {
    emit(i)
    delay(100)
    }
    }
    suspend fun main() {
    foo()
    .map { it * it }
    .toList()
    }

    View Slide

  140. Why flow?

    View Slide

  141. A
    A’
    mapper
    fun map(mapper: (T) -> R): Flux
    fun flatMap(mapper: (T) -> Publisher): Flux
    Synchronous
    Asynchronous
    Flux

    View Slide

  142. A
    A’
    mapper
    fun map(mapper: (T) -> R): Flux
    fun flatMap(mapper: (T) -> Publisher): Flux
    fun filter(predicate: (T) -> Boolean): Flux
    A
    A
    predicate
    Synchronous
    Asynchronous
    Synchronous
    Asynchronous
    Flux
    fun filterWhen(predicate: (T) -> Publisher): Flux

    View Slide

  143. A
    A’
    Flow
    fun map(transform: suspend (T) -> R): Flow
    transform

    View Slide

  144. A
    A’
    transform
    Flow
    fun map(transform: suspend (T) -> R): Flow

    View Slide

  145. A
    A’
    transform fun map(transform: suspend (T) -> R): Flow
    Flow
    fun filter(predicate: suspend (T) -> Boolean): Flow
    A
    predicate
    A

    View Slide

  146. Operator avoidance
    startWith(value) onStart { emit(value) }
    delaySubscription(time) onStart { delay(time) }
    startWith(flow) onStart { emitAll(flow) }
    delayElements(time) onEach { delay(time) }
    onErrorReturn(value) catch { emit(value) }
    onErrorResume(flow) catch { emitAll(flow) }
    generate(…) flow { … }
    Composable

    View Slide

  147. Reactive + = ❤

    View Slide

  148. Spring ❤ Flow
    @GetMapping("/sse/{n}", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
    fun greetings(@PathVariable n: String): Flow

    View Slide

  149. Spring ❤ Flow
    @GetMapping("/sse/{n}", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
    fun greetings(@PathVariable n: String): Flow =
    flow {
    while(true) {
    emit(Greeting("Hello from coroutines $n @ ${Instant.now()}"))
    delay(1000)
    }
    }

    View Slide

  150. Demo: Spring Flow Support

    View Slide

  151. Thank you
    Questions?
    elizarov @
    relizarov

    View Slide

  152. Kotlin Coroutines For The Win!
    #springone
    @s1p

    View Slide