Coroutines and Reactive Programming friends or foes? Konrad Kami - - PowerPoint PPT Presentation

coroutines and reactive programming friends or foes
SMART_READER_LITE
LIVE PREVIEW

Coroutines and Reactive Programming friends or foes? Konrad Kami - - PowerPoint PPT Presentation

Coroutines and Reactive Programming friends or foes? Konrad Kami ski Allegro.pl suspend fun getUser(userId: Int): User? fun getDefaultUserName(userId: Int): String val name = getUser (userId)?.name ?: getDefaultUserName (userId) println


slide-1
SLIDE 1

Allegro.pl

Konrad Kamiński

Coroutines and Reactive Programming – friends or foes?

slide-2
SLIDE 2

suspend fun getUser(userId: Int): User? fun getDefaultUserName(userId: Int): String val name = getUser(userId)?.name ?: getDefaultUserName(userId) println(name) fun getUser(userId: Int): Mono<User> fun getDefaultUserName(userId: Int): String getUser(userId) .map { it.name } .switchIfEmpty(Mono.fromCallable { getDefaultUserName(userId) }) .subscribe { name -> println(name) }

slide-3
SLIDE 3

suspend fun getUser(userId: Int): User? fun getDefaultUserName(userId: Int): String val name = getUser(userId)?.name ?: getDefaultUserName(userId) println(name) fun getUser(userId: Int): Mono<User> fun getDefaultUserName(userId: Int): String getUser(userId) .map { it.name } .switchIfEmpty(Mono.fromCallable { getDefaultUserName(userId) }) .subscribe { name -> println(name) }

slide-4
SLIDE 4

suspend fun getUser(userId: Int): User? fun getDefaultUserName(userId: Int): String val name = getUser(userId)?.name ?: getDefaultUserName(userId) println(name) fun getUser(userId: Int): Mono<User> fun getDefaultUserName(userId: Int): String getUser(userId) .map { it.name } .switchIfEmpty(Mono.fromCallable { getDefaultUserName(userId) }) .subscribe { name -> println(name) }

slide-5
SLIDE 5

Sequential code

slide-6
SLIDE 6

suspend fun getUser(userId: Int): User fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo

slide-7
SLIDE 7

suspend fun getUser(userId: Int): User fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo fun getUser(userId: Int): Mono<User> fun getAccount(accountId: Int): Account fun getAccountNo(userId: Int): Mono<String> = getUser(userId) .map { getAccount(it.accountId).accountNo }

slide-8
SLIDE 8

suspend fun getUser(userId: Int): User fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo

slide-9
SLIDE 9

suspend fun getUser(userId: Int): User suspend fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo

slide-10
SLIDE 10

suspend fun getUser(userId: Int): User suspend fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo fun getUser(userId: Int): Mono<User> fun getAccount(accountId: Int): Account fun getAccountNo(userId: Int): Mono<String> = getUser(userId) .map { getAccount(it.accountId).accountNo }

slide-11
SLIDE 11

suspend fun getUser(userId: Int): User suspend fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo fun getUser(userId: Int): Mono<User> fun getAccount(accountId: Int): Mono<Account> fun getAccountNo(userId: Int): Mono<String> = getUser(userId) .map { getAccount(it.accountId).accountNo }

slide-12
SLIDE 12

suspend fun getUser(userId: Int): User suspend fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo fun getUser(userId: Int): Mono<User> fun getAccount(accountId: Int): Mono<Account> fun getAccountNo(userId: Int): Mono<String> = getUser(userId) .map { getAccount(it.accountId).map(Account::accountNo) }

slide-13
SLIDE 13

suspend fun getUser(userId: Int): User suspend fun getAccount(accountId: Int): Account suspend fun getAccountNo(userId: Int): String = getAccount(getUser(userId).accountId).accountNo fun getUser(userId: Int): Mono<User> fun getAccount(accountId: Int): Mono<Account> fun getAccountNo(userId: Int): Mono<String> = getUser(userId) .flatMap { getAccount(it.accountId).map(Account::accountNo) }

slide-14
SLIDE 14

Threads

slide-15
SLIDE 15

suspend fun getUser(userId: Int): User val userServiceContext = newFixedThreadPoolContext(5, "user") suspend fun getUserName(userId: Int): String = withContext(userServiceContext) { getUser(userId).name }

slide-16
SLIDE 16

suspend fun getUserName(userId: Int): String suspend fun calculateEncryptionKey(name: String): String val encryptionContext = newFixedThreadPoolContext(5, "encryption") suspend fun getUserEncryptionKey(userId: Int): String = withContext(encryptionContext) { calculateEncryptionKey(getUserName(userId)) }

slide-17
SLIDE 17

suspend fun getUserName(userId: Int): String suspend fun calculateEncryptionKey(name: String): String val encryptionContext = newFixedThreadPoolContext(5, "encryption") suspend fun getUserEncryptionKey(userId: Int): String = withContext(encryptionContext) { calculateEncryptionKey(getUserName(userId)) } fun getUser(userId: Int): Mono<User> val userScheduler = Schedulers.newParallel("user", 5) fun getUserName(userId: Int): Mono<String> = getUser(userId) .map { user -> user.name } .subscribeOn(userScheduler)

slide-18
SLIDE 18

suspend fun getUserName(userId: Int): String suspend fun calculateEncryptionKey(name: String): String val encryptionContext = newFixedThreadPoolContext(5, "encryption") suspend fun getUserEncryptionKey(userId: Int): String = withContext(encryptionContext) { calculateEncryptionKey(getUserName(userId)) } fun getUserName(userId: Int): Mono<String> fun calculateEncryptionKey(name: String): String val encryptionScheduler = Schedulers.newParallel("encryption", 5) fun getUserEncryptionKey(name: String): Mono<String> = getUserName(userId) .publishOn(encryptionScheduler) .map { user -> calculateEncryptionKey(user) }

slide-19
SLIDE 19

Request context

slide-20
SLIDE 20

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String =

slide-21
SLIDE 21

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String = loggingGetUser(userId).name

slide-22
SLIDE 22

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String = withContext(requestId.asContextElement()) { loggingGetUser(userId).name }

slide-23
SLIDE 23

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String = withContext(requestId.asContextElement()) { loggingGetUser(userId).name } fun getUser(userId: Int): Mono<User> val requestId: ThreadLocal<String> fun loggingGetUser(userId: Int): Mono<User> = getUser(userId).map { user -> log("${requestId.get()}: $userId").let { user } }

slide-24
SLIDE 24

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String = withContext(requestId.asContextElement()) { loggingGetUser(userId).name } fun getUser(userId: Int): Mono<User> val requestId: ThreadLocal<String> fun loggingGetUser(userId: Int): Mono<User> = getUser(userId).flatMap { user -> Mono.subscriberContext().map { context -> log("${context.get<String>("reqId")}: $userId").let { user } }} fun getUserName(userId: Int): Mono<String> =

slide-25
SLIDE 25

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String = withContext(requestId.asContextElement()) { loggingGetUser(userId).name } fun getUser(userId: Int): Mono<User> val requestId: ThreadLocal<String> fun loggingGetUser(userId: Int): Mono<User> = getUser(userId).flatMap { user -> Mono.subscriberContext().map { context -> log("${context.get<String>("reqId")}: $userId").let { user } }} fun getUserName(userId: Int): Mono<String> = loggingGetUser(userId).map { it.name }

slide-26
SLIDE 26

suspend fun getUser(userId: Int): User val requestId: ThreadLocal<String> suspend fun loggingGetUser (userId: Int): User = log("${requestId.get ()}: $userId").let { getUser(userId) } suspend fun getUserName(userId: Int): String = withContext(requestId.asContextElement()) { loggingGetUser(userId).name } fun getUser(userId: Int): Mono<User> val requestId: ThreadLocal<String> fun loggingGetUser(userId: Int): Mono<User> = getUser(userId).flatMap { user -> Mono.subscriberContext().map { context -> log("${context.get<String>("reqId")}: $userId").let { user } }} fun getUserName(userId: Int): Mono<String> = loggingGetUser(userId).map { it.name } .subscriberContext(Context.of("reqId", requestId.get()))

slide-27
SLIDE 27

Exception handling

slide-28
SLIDE 28

suspend fun getUser(userId: Int): User suspend fun getUserName(userId: Int): String = try { getUser(userId).name } catch (e: UserNotFoundException) { "Unknown: $userId" }

slide-29
SLIDE 29

suspend fun getUser(userId: Int): User suspend fun getUserName(userId: Int): String = try { getUser(userId).name } catch (e: UserNotFoundException) { "Unknown: $userId" } fun getUser(userId: Int): Mono<User> fun getUserName(userId: Int): Mono<String> = getUser(userId) .map { it.name } .onErrorReturn("Unknown: $userId") // onErrorResume // onErrorMap

slide-30
SLIDE 30

Retrying

slide-31
SLIDE 31

suspend fun getUser(userId: Int): User suspend fun retryingGetUser(userId: Int): User { lateinit var ex: Exception repeat(5) { counter -> try { return getUser(userId) } catch (e: Exception) { delay((counter+1L)*500); ex = e } } throw ex }

slide-32
SLIDE 32

suspend fun <T> retry(n: Int, delayMillis: Int, fn: suspend () -> T): T { lateinit var ex: Exception repeat(n) { counter -> try { return fn() } catch (e: Exception) { delay((counter+1L)*delayMillis); ex = e } } throw ex }

slide-33
SLIDE 33

suspend fun getUser(userId: Int): User suspend fun <T> retry(n: Int, delayMillis: Int, fn: suspend () -> T): T suspend fun retryingGetUser(userId: Int): User = retry(5, 500) { getUser(userId) }

slide-34
SLIDE 34

suspend fun getUser(userId: Int): User suspend fun <T> retry(n: Int, delayMillis: Int, fn: suspend () -> T): T suspend fun retryingGetUser(userId: Int): User = retry(5, 500) { getUser(userId) } fun getUser(userId: Int): Mono<User> fun retryFunc(n: Int, millis: Int, ex: Flux<Throwable>): Flux<Long> fun retryingGetUser(userId: Int): Mono<User> = getUser(userId) .retryWhen { exceptions -> retryFunc(5, 500, exceptions) }

slide-35
SLIDE 35

suspend fun getUser(userId: Int): User suspend fun <T> retry(n: Int, delayMillis: Int, fn: suspend () -> T): T suspend fun retryingGetUser(userId: Int): User = retry(5, 500) { getUser(userId) } fun retryFunc(n: Int, millis: Int, ex: Flux<Throwable>): Flux<Long> = ex.zipWith(Flux.range(1, n), BiFunction { error: Throwable, index: Int -> if (index < n) index.toLong() else throw Exceptions.propagate(error) }) .flatMap { index -> Mono.delay(Duration.ofMillis(index * millis)) }

slide-36
SLIDE 36

Concurrent code

slide-37
SLIDE 37

suspend fun getUser(userId: Int): User suspend fun getRoles(userId: Int): Roles suspend fun getUserWithRoles(userId: Int): Pair<User, Roles> = coroutineScope { val user: Deferred<User> = async { getUser(userId) } val roles: Deferred<Roles> = async { getRoles(userId) } user.await() to roles.await() }

slide-38
SLIDE 38

suspend fun getUser(userId: Int): User suspend fun getRoles(userId: Int): Roles suspend fun getUserWithRoles(userId: Int): Pair<User, Roles> = coroutineScope { val user: Deferred<User> = async { getUser(userId) } val roles: Deferred<Roles> = async { getRoles(userId) } user.await() to roles.await() } fun getUser(userId: Int): Mono<User> fun getRoles(userId: Int): Mono<Roles> fun getUserWithRoles(userId: Int): Mono<Pair<User, Roles> > = getUser(userId) .zipWith(getRoles(userId)) { user, roles -> user to roles }

slide-39
SLIDE 39

Cancellation

slide-40
SLIDE 40

suspend fun getUserWithRoles(userId: Int): Pair<User, Roles> = coroutineScope { val userDef = async { getUser(userId) } val rolesDef = async { getRoles(userId) } val user = userDef.await() when (user.isBlocked()) { true -> { rolesDef.cancel(); user to Roles.EMPTY } else -> user to rolesDef.await() }}

slide-41
SLIDE 41

suspend fun getUserWithRoles(userId: Int): Pair<User, Roles> = coroutineScope { val userDef = async { getUser(userId) } val rolesDef = async { getRoles(userId) } val user = userDef.await() when (user.isBlocked()) { true -> { rolesDef.cancel(); user to Roles.EMPTY } else -> user to rolesDef.await() }} fun getUserWithRoles(userId: Int): Mono<Pair<User, Roles>> { val user = getUser(userId).flux().share().single() return Mono.first( user.flatMap { u -> if (u.isBlocked()) Mono.just(u to Roles.EMPTY) else Mono.never() }, user.zipWith<Roles, Pair<User, Roles>>(getRoles(userId)) { u,r -> u to (if (u.isBlocked()) Roles.EMPTY else r) } ) }

slide-42
SLIDE 42

Parallel code

slide-43
SLIDE 43

suspend fun getUserIds(accountId: Int): List<Int> suspend fun getUser(userId: Int): User suspend fun getUsers(accountId: Int): List<User> = coroutineScope { getUserIds(accountId) // List<Int> }

slide-44
SLIDE 44

suspend fun getUserIds(accountId: Int): List<Int> suspend fun getUser(userId: Int): User suspend fun getUsers(accountId: Int): List<User> = coroutineScope { getUserIds(accountId) // List<Int> .map { userId -> async { getUser(userId) } } // List<Deferred<User>> }

slide-45
SLIDE 45

suspend fun getUserIds(accountId: Int): List<Int> suspend fun getUser(userId: Int): User suspend fun getUsers(accountId: Int): List<User> = coroutineScope { getUserIds(accountId) // List<Int> .map { userId -> async { getUser(userId) } } // List<Deferred<User>> .awaitAll() }

slide-46
SLIDE 46

suspend fun getUserIds(accountId: Int): List<Int> suspend fun getUser(userId: Int): User suspend fun getUsers(accountId: Int): List<User> = coroutineScope { getUserIds(accountId) // List<Int> .map { userId -> async { getUser(userId) } } // List<Deferred<User>> .awaitAll() } fun getUserIds(accountId: Int): Mono<List<Int>> fun getUser(userId: Int): Mono<User> fun getUsers(accountId: Int): Mono<List<User>> = getUserIds(accountId).flatMapIterable { it } .flatMap { getUser(it) }.collectList()

slide-47
SLIDE 47

suspend fun getUserIds(accountId: Int): List<Int> suspend fun getUser(userId: Int): User suspend fun getUsers(accountId: Int): List<User> = coroutineScope { getUserIds(accountId) // List<Int> .map { userId -> async { getUser(userId) } } // List<Deferred<User>> .awaitAll() } fun getUserIds(accountId: Int): Flux<Int> fun getUser(userId: Int): Mono<User> fun getUsers(accountId: Int): Flux<User> = getUserIds(accountId) .flatMap { getUser(it) }

slide-48
SLIDE 48

Streams

slide-49
SLIDE 49

suspend fun getUsers(accountId: Int): ReceiveChannel<User> suspend fun printUsers(accountId: Int) { val users = getUsers(accountId) while (true) { val user = channel.receive() println(user.name) } }

slide-50
SLIDE 50

suspend fun getUsers(accountId: Int): ReceiveChannel<User> suspend fun printUsers(accountId: Int) { val users = getUsers(accountId) while (true) { val user = channel.receiveOrNull() if (user != null) println(user.name) else break } }

slide-51
SLIDE 51

suspend fun getUsers(accountId: Int): ReceiveChannel<User> suspend fun printUsers(accountId: Int) { val users = getUsers(accountId) users.consumeEach { user -> println(user.name) } }

slide-52
SLIDE 52

suspend fun getUsers(accountId: Int): ReceiveChannel<User> suspend fun printUsers(accountId: Int) { val users = getUsers(accountId) users.consumeEach { user -> println(user.name) } } fun getUsers(accountId: Int): Flux<User> fun printUsers(accountId: Int) { getUsers(accountId) .subscribe { user -> println(user.name) } }

slide-53
SLIDE 53

Stream generation

slide-54
SLIDE 54

suspend fun CoroutineScope.getUsers(accountId: Int): ReceiveChannel<User> = Channel<User>().apply { launch { send(user1) send(user2) close() } }

slide-55
SLIDE 55

suspend fun CoroutineScope.getUsers(accountId: Int): ReceiveChannel<User> = produce { send(user1) // same as channel.send(user1) send(user2) close() }

slide-56
SLIDE 56

suspend fun CoroutineScope.getUsers(accountId: Int): ReceiveChannel<User> = produce { send(user1) // same as channel.send(user1) send(user2) close() } fun getUsers(accountId: Int): Flux<User> = Flux.generate { sink -> sink.next(user1) sink.next(user2) sink.complete() }

slide-57
SLIDE 57

Operators

slide-58
SLIDE 58

suspend fun getUsers(accountId: Int): ReceiveChannel<User> suspend fun getUserNames(accountId: Int): ReceiveChannel<String> = getUsers(accountId) .filterNot { it.blocked } .map { it.name }

slide-59
SLIDE 59

suspend fun getUsers(accountId: Int): ReceiveChannel<User> suspend fun getUserNames(accountId: Int): ReceiveChannel<String> = getUsers(accountId) .filterNot { it.blocked } .map { it.name } fun getUsers(accountId: Int): Flux<User> fun getUserNames(accountId: Int): Flux<String> = getUsers(accountId) .filterNot { it.blocked } .map { it.name }

slide-60
SLIDE 60

Custom operator

slide-61
SLIDE 61

fun <T, R> ReceiveChannel<T>.filterMap (fn: (T) -> R?) = GlobalScope.produce<R> { consumeEach { t -> val r = fn.invoke(t) if (r != null) send(r) } } 300 lines of code for filter 260 lines of code for map

slide-62
SLIDE 62

Backpressure

slide-63
SLIDE 63

suspend fun CoroutineScope.getUsers(accountId: Int): ReceiveChannel<User> = Channel<User>().apply { launch { send(user1) send(user2) close() } }

slide-64
SLIDE 64

val rendezvousChannel = Channel<User>() val rendezvousChannel = Channel<User>(RENDEZVOUS) val bufferedChannel = Channel<User>(5) val unlimitedChannel = Channel<User>(UNLIMITED) val conflatedChannel = Channel<User>(CONFLATED)

slide-65
SLIDE 65

val rendezvousChannel = Channel<User>() val rendezvousChannel = Channel<User>(RENDEZVOUS) val bufferedChannel = Channel<User>(5) val unlimitedChannel = Channel<User>(UNLIMITED) val conflatedChannel = Channel<User>(CONFLATED) val flux: Flux<User>() val bufferedFlux = flux.onBackpressureBuffer(bufferSize) val droppingFlux = flux.onBackpressureDrop() val latestFlux = flux.onBackpressureLatest()

slide-66
SLIDE 66

Interoparability

slide-67
SLIDE 67

fun reactiveGetUser(userId: Int): Mono<User> suspend fun getUser(userId: Int): User? = reactiveGetUser(userId).awaitFirstOrNull() // awaitFirst() // awaitFirstOrDefault(defaultValue: T) // awaitFirstOrElse(defaultValue: () -> T)

slide-68
SLIDE 68

fun reactiveGetUser(userId: Int): Mono<User> suspend fun getUser(userId: Int): User? = reactiveGetUser(userId).awaitFirstOrNull() // awaitFirst() // awaitFirstOrDefault(defaultValue: T) // awaitFirstOrElse(defaultValue: () -> T) suspend fun suspendingGetUser(userId: Int): User? fun CoroutineScope.getUser(userId: Int): Mono<User> = mono { suspendingGetUser(userId) }

slide-69
SLIDE 69

fun getUsersFlux(accountId: Int): Flux<User> fun getUsers(accountId: Int): ReceiveChannel<User> = reactiveGetUsers(accountId).openSubscription()

slide-70
SLIDE 70

fun getUsersFlux(accountId: Int): Flux<User> fun getUsers(accountId: Int): ReceiveChannel<User> = reactiveGetUsers(accountId).openSubscription() fun getUsersChannel(accountId: Int): ReceiveChannel<User> fun CoroutineScope.getUsers(accountId: Int): Flux<User> = flux { getUsersChannel(accountId).consumeEach { user -> send(user) } }

slide-71
SLIDE 71

Coroutines or Reactive Programming?

  • Sequential code is where coroutines shine
  • Concurrent code complexity depends a lot on your

use case

  • Channels are hot and experimental
  • You can mix both solutions
slide-72
SLIDE 72

Where to find more information


  • Guide to reactive streams with coroutines

https://bit.ly/2xNLF1m

  • Corotoutines guide

https://bit.ly/2NTUoZS

slide-73
SLIDE 73

Allegro.pl

Konrad Kamiński

Thank you!