SlideShare a Scribd company logo
Kotlin Coroutines 톺아보기
발 표 자 : 김태우(당근마켓 로컬 커머스 팀)
밋업자료 :
밋업코드 : https://github.com/gaaon/kotlin-coroutines-examples
00 비동기에 대한 고민
01 Coroutine 소개
02 Coroutine 톺아보기
03 다음회 예고
목차
목차
- 로컬 커머스팀 백엔드 개발자
- 커머스에서 로컬의 가치를 찾는 팀
시작하기에 앞서
비동기에 대한 고민
00
- 한번에 이해하기 힘들다
- 추적이 어렵다
- 에러 핸들링이 어렵다
00. 비동기에 대한 고민
userRepository.findUserByIdAsMaybe(userId)
.subscribe { buyer ->
addressRepository.findAddressByUserAsPublisher(buyer)
.subscribe(LastItemSubscriber { address ->
checkValidRegion(address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
check(products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(products)
.collect().asList()
.subscribe().with { stores ->
check(stores.isNotEmpty())
orderRepository.createOrderAsFuture(
buyer, products, stores, address
).whenComplete { order, _ ->
emitter.success(order)
}
}
}
})
}
동기 프로그래밍과 다르다
- 어떻게 혼용해서 써야 할까?
- 어떤 결과 타입을 반환해야 할까?
- 또 다른 비동기 라이브러리가
추가되면?
00. 비동기에 대한 고민
다양한 비동기 라이브러리
- 우수한 가독성
- 에러 핸들링
- 동시성 처리
- Flow
- Channel
00. 비동기에 대한 고민
Coroutine이 해결사? suspend fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList()
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList()
check(stores.isNotEmpty())
// 5. 주문 생성
val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await()
return order
}
Coroutine 소개
01
- 구매자 정보
- 상품 정보
- 스토어 정보
- 배송지
01. Coroutine 소개
상품 주문
동기 코드로
- 구매자 조회
- 주소 조회 및 유효성 체크
- 상품 목록 조회
- 스토어 목록 조회
- 주문 생성
01. Coroutine 소개
주문 생성 동기 코드 fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdSync(userId)
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserSync(buyer).last()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsSync(productIds)
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsSync(products)
check(stores.isNotEmpty())
// 5. 주문 생성
val order =
orderRepository.createOrderSync(buyer, products, stores, address)
return order
}
비동기 코드로
- rxjava3의 Maybe
- 0 .. 1, Error
01. Coroutine 소개
구매자 조회 import io.reactivex.rxjava3.core.Maybe
interface UserAsyncRepository {
fun findUserByIdAsMaybe(userId: String): Maybe<User>
}
class UserRxRepository : UserRepositoryBase(), UserAsyncRepository {
override fun findUserByIdAsMaybe(userId: String): Maybe<User> {
val user = prepareUser(userId)
return Maybe.just(user)
.delay(TIME_DELAY_MS, TimeUnit.MILLISECONDS)
}
}
- jdk9의 Flow 사용
- item을 publish 하고 complete
이벤트로 flow 종료
01. Coroutine 소개
주소 조회 import java.util.concurrent.Flow
interface AddressAsyncRepository {
fun findAddressByUserAsPublisher(user: User): Flow.Publisher<Address>
}
- reactor의 Flux 사용
- 0 .. n, Error
01. Coroutine 소개
상품 조회 import reactor.core.publisher.Flux
interface ProductAsyncRepository {
fun findAllProductsByIdsAsFlux(productIds: List<String>): Flux<Product>
}
- mutiny의 Multi 사용
- 0 .. n, Error
01. Coroutine 소개
스토어 조회 import io.smallrye.mutiny.Multi
interface StoreAsyncRepository {
fun findStoresByProductsAsMulti(products: List<Product>): Multi<Store>
}
- jdk8의 CompletableFuture
- complete 되는 시점에 결과 반환
01. Coroutine 소개
주문 생성 import java.util.concurrent.CompletableFuture
interface OrderAsyncRepository {
fun createOrderAsFuture(
buyer: User,
products: List<Product>,
stores: List<Store>,
address: Address,
): CompletableFuture<Order>
}
- subscribe는 결과를 얻은 시점에
주어진 subscriber (consumer)를
실행하는 일종의 callback
- 반환값들이 아래에서 계속 필요해서
subscribe가 중첩
01. Coroutine 소개
subscribe hell fun execute(inputValues: InputValues): Mono<Order> {
val (userId, productIds) = inputValues
return Mono.create { emitter ->
userRepository.findUserByIdAsMaybe(userId)
.subscribe { buyer ->
addressRepository.findAddressByUserAsPublisher(buyer)
.subscribe(LastItemSubscriber { address ->
checkValidRegion(address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
check(products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(products)
.collect().asList()
.subscribe().with { stores ->
check(stores.isNotEmpty())
orderRepository.createOrderAsFuture(
buyer, products, stores, address
).whenComplete { order, _ ->
emitter.success(order)
}
}
}
})
}
}
}
- 각각의 비동기 함수를 Reactor로
변경
- RxJava3Adapter
- JdkFlowAdapter
- Flux.collectList
- Flux.from
- Mono.fromFuture
01. Coroutine 소개
flatMap hell fun execute(inputValues: InputValues): Mono<Order> {
val (userId, productIds) = inputValues
return RxJava3Adapter.maybeToMono(userRepository.findUserByIdAsMaybe(userId))
.flatMap { buyer ->
JdkFlowAdapter.flowPublisherToFlux(
addressRepository.findAddressByUserAsPublisher(buyer))
.last()
.flatMap { address ->
checkValidRegion(address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.flatMap { products ->
check(products.isNotEmpty())
Flux.from(storeRepository.findStoresByProductsAsMulti(products)
.collectList()
.flatMap { stores ->
check(stores.isNotEmpty())
Mono.fromFuture(
orderRepository.createOrderAsFuture(
buyer, products, stores, address
)
)
}
}
}
}
}
Coroutine으로
- Maybe<T>.awaitSingle
- Publisher<T>.awaitLast
- Flow<T>.toList
- CompletableFuture<T>.await
01. Coroutine 소개
Coroutine suspend fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList()
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList()
check(stores.isNotEmpty())
// 5. 주문 생성
val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await()
return order
}
01. Coroutine 소개
suspend fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList()
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList()
check(stores.isNotEmpty())
// 5. 주문 생성
val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await()
return order
}
fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdSync(userId)
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserSync(buyer).last()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsSync(productIds)
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsSync(products)
check(stores.isNotEmpty())
// 5. 주문 생성
val order = orderRepository.createOrderSync(buyer, products, stores, address)
return order
}
동기코드 그리고 Coroutine
01. Coroutine 소개
Coroutine 실행 @Test
fun `should return a createdOrder in coroutine`() = runBlocking {
// given
val userId = "user1"
val productIds = listOf("product1", "product2", "product3")
// when
val watch = StopWatch().also { it.start() }
val inputValues = CreateOrderCoroutineUseCase.InputValues(userId, productIds)
val createdOrder = createOrderUseCase.execute(inputValues)
watch.stop()
println("Time Elapsed: ${watch.time}ms")
// then
println(createdOrder)
}
- runBlocking은 동기코드에서
coroutine을 실행할 수 있게
bridge 역할
이 앞에는
코드가 많습니다
주의
Coroutine 톺아보기
02
- 경량화된 쓰레드?
- 특정 지점에서 정지했다가 재개할 수
있는 쓰레드?
02. Coroutine 톺아보기
Coroutine? suspend fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList()
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList()
check(stores.isNotEmpty())
// 5. 주문 생성
val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await()
return order
}
- Finite State Machine 기반의
재귀 함수로 변환
kotlin 컴파일러가 suspend가 붙은 함수에
추가적인 코드를 추가
- Continuation 인자를 타겟 함수에
추가하고 Continuation 구현체를
생성
- 타겟 함수 내의 모든 suspend 함수에
생성한 continuation 객체를 패스
- 코드를 분리해서 switch case 안에
넣고 label을 이용해서 state를 변경
02. Coroutine 톺아보기
Kotlin Compiler
- execute 함수가 실행되면 재귀
호출을 이용해서 스스로(execute
함수)를 실행하면서 state를 변경
- state가 최종에 도달하면 값을
caller에 반환
02. Coroutine 톺아보기
FSM 기반의 재귀 함수
재귀호출 재귀호출
재귀호출
재귀호출 complete
FSM 기반의 동기 코드로
- SharedData를 통해서 여러 가지
context를 저장
- label은 state machine의 현재 state
값
- 이전 state에서 찾은 값들을 buyer,
address, products, stores, order에
저장
- resumeWith로 재귀 호출을 하고
결과를 result에 저장
- 인자의 sharedData가 null이라면
생성하고 아니면 있는 sharedData를
사용
02. Coroutine 톺아보기
FSM 기반의 동기 코드 class SharedData {
var label: Int = 0
lateinit var result: Any
lateinit var buyer: User
lateinit var address: Address
lateinit var products: List<Product>
lateinit var stores: List<Store>
lateinit var order: Order
lateinit var resumeWith: (result: Any) -> Order
}
fun execute(
inputValues: InputValues,
sharedData: SharedData? = null,
): Order {
val (userId, productIds) = inputValues
val that = this
val shared = sharedData ?: SharedData().apply {
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; state 0
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; state 1
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; state 2
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; state 3
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; state 4
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; state 5
return when (shared.label) {
0 -> {
shared.label = 1
userRepository.findUserByIdSync(userId)
.let { user ->
shared.resumeWith(user)
}
}
1 -> {
shared.label = 2
shared.buyer = shared.result as User
addressRepository.findAddressByUserSync(shared.buyer).last()
.let { address ->
shared.resumeWith(address)
}
}
2 -> {
shared.label = 3
shared.address = shared.result as Address
checkValidRegion(shared.address)
productRepository.findAllProductsByIdsSync(productIds)
.let { products ->
shared.resumeWith(products)
}
}
3 -> {
shared.label = 4
shared.products = shared.result as List<Product>
check(shared.products.isNotEmpty())
storeRepository.findStoresByProductsSync(shared.products)
.let { stores ->
shared.resumeWith(stores)
}
}
4 -> {
shared.label = 5
shared.stores = shared.result as List<Store>
check(shared.stores.isNotEmpty())
orderRepository.createOrderSync(
shared.buyer, shared.products, shared.stores, shared.address
).let { order ->
shared.resumeWith(order)
}
}
5 -> {
shared.order = shared.result as Order
shared.order
}
else -> throw IllegalAccessException()
}
this.resumeWith = fun (result: Any): Order {
this.result = result
return that.execute(inputValues, this)
}
02. Coroutine 톺아보기
FSM 기반의 동기 코드 ; 실행 @Test
fun `should return a createdOrder in sync with state machine`() {
// given
val userId = "user1"
val productIds = listOf("product1", "product2", "product3")
// when
val watch = StopWatch().also { it.start() }
val inputValues = CreateOrderSyncStateMachineUseCase.InputValues(
userId, productIds)
val createdOrder = createOrderUseCase.execute(inputValues)
watch.stop()
println("Time Elapsed: ${watch.time}ms")
// then
println(createdOrder)
}
- execute를 실행할때, 두번째
인자를 넘기지 않아서 default
value인 null로 제공
FSM 기반의 비동기 코드로
- SharedDataContinuation를 통해서
여러 가지 context를 저장
- label은 state machine의 현재 state
값
- 이전 state에서 찾은 값들을 buyer,
address, products, stores, order에
저장
- resumeWith로 재귀 호출을 하여
결과를 result에 저장
- 인자의 sharedData가
SharedDataContinuation 타입이
아니라면 생성
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 class SharedDataContinuation( // 실제로 continuation 구현체는 method
안에
val completion: Continuation<Any>,
) : Continuation<Any> {
var label: Int = 0
lateinit var result: Any
lateinit var buyer: User
lateinit var address: Address
lateinit var products: List<Product>
lateinit var stores: List<Store>
lateinit var order: Order
lateinit var resume: () -> Unit
override val context: CoroutineContext = completion.context
override fun resumeWith(result: Result<Any>) {
this.result = result
this.resume()
}
}
fun execute(inputValues: InputValues, completion: Continuation<Any>) {
val (userId, productIds) = inputValues
val that = this
val cont = completion as? SharedDataContinuation
?: SharedDataContinuation(completion).apply {
resume = fun() {
// recursive self
that.execute(inputValues, this)
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 class SharedDataContinuation(
val completion: Continuation<Any>,
) : Continuation<Any> {
var label: Int = 0
lateinit var result: Any
lateinit var buyer: User
lateinit var address: Address
lateinit var products: List<Product>
lateinit var stores: List<Store>
lateinit var order: Order
lateinit var resume: () -> Unit
override val context: CoroutineContext = completion.context
override fun resumeWith(result: Result<Any>) {
this.result = result
this.resume()
}
}
fun execute(inputValues: InputValues, completion: Continuation<Any>) {
val (userId, productIds) = inputValues
val that = this
val cont = completion as? SharedDataContinuation
?: SharedDataContinuation(completion).apply {
resume = fun() {
// recursive self
that.execute(inputValues, this)
}
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this
continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine
passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; 실행
- testContinuation을 생성해서
execute 함수에 주입
@Test
fun `should return a createdOrder in async with state machine`() {
// given
val userId = "user1"
val productIds = listOf("product1", "product2", "product3")
// when
val watch = StopWatch().also { it.start() }
val lock = CountDownLatch(1)
val testContinuation = object : Continuation<Any> {
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Any>) {
watch.stop()
lock.countDown()
println("Time Elapsed: ${watch.time}ms")
println(result.getOrThrow())
}
}
val inputValues = CreateOrderAsyncStateMachine3UseCase.InputValues(userId, productIds)
createOrderUseCase.execute(inputValues, testContinuation)
// then
lock.await(3000, TimeUnit.MILLISECONDS)
}
02. Coroutine 톺아보기
FSM 기반의 비동기 코드
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; state 0
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
override fun resumeWith(result: Result<Any>) {
this.result = result
that.execute(inputValues, this)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; state 1
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
override fun resumeWith(result: Result<Any>) {
this.result = result
that.execute(inputValues, this)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; state 2
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
override fun resumeWith(result: Result<Any>) {
this.result = result
that.execute(inputValues, this)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; state 3
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
override fun resumeWith(result: Result<Any>) {
this.result = result
that.execute(inputValues, this)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; state 4
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
override fun resumeWith(result: Result<Any>) {
this.result = result
that.execute(inputValues, this)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; state 5
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
override fun resumeWith(result: Result<Any>) {
this.result = result
that.execute(inputValues, this)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; 최종 class SharedDataContinuation(
val completion: Continuation<Any>,
) : Continuation<Any> {
var label: Int = 0
lateinit var result: Any
lateinit var buyer: User
lateinit var address: Address
lateinit var products: List<Product>
lateinit var stores: List<Store>
lateinit var order: Order
lateinit var resume: () -> Unit
override val context: CoroutineContext = completion.context
override fun resumeWith(result: Result<Any>) {
this.result = result
this.resume()
}
}
fun execute(inputValues: InputValues, completion: Continuation<Any>) {
val (userId, productIds) = inputValues
val that = this
val cont = completion as? SharedDataContinuation
?: SharedDataContinuation(completion).apply {
resume = fun() {
// recursive self
that.execute(inputValues, this)
}
- completion은 외부에서 주입하는
continuation 기반의 객체
02. Coroutine 톺아보기
FSM 기반의 비동기 코드 ; 실행
- testContinuation을 생성해서
execute 함수에 주입
@Test
fun `should return a createdOrder in async with state machine`() {
// given
val userId = "user1"
val productIds = listOf("product1", "product2", "product3")
// when
val watch = StopWatch().also { it.start() }
val lock = CountDownLatch(1)
val testContinuation = object : Continuation<Any> {
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Any>) {
watch.stop()
lock.countDown()
println("Time Elapsed: ${watch.time}ms")
println(result.getOrThrow())
}
}
val inputValues = CreateOrderAsyncStateMachine3UseCase.InputValues(userId, productIds)
createOrderUseCase.execute(inputValues, testContinuation)
// then
lock.await(3000, TimeUnit.MILLISECONDS)
}
Coroutine으로
02. Coroutine 톺아보기
FSM 기반의 Coroutines ; 시작
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
02. Coroutine 톺아보기
FSM 기반의 Coroutines ; extension function
fun <T: Any> Maybe<T>.awaitSingle(cont: Continuation<Any>) {
this.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
fun <T: Any> Flow.Publisher<T>.awaitLast(cont: Continuation<Any>) {
this.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
fun <T: Any> Flux<T>.toList(cont: Continuation<Any>) {
this.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
fun <T: Any> Multi<T>.toList(cont: Continuation<Any>) {
this.collect()
.asList()
.subscribeAsCompletionStage()
.whenComplete { stores, _ ->
cont.resumeWith(Result.success(stores))
}
}
fun <T: Any> CompletionStage<T>.awaitSingle(cont:
- 각각의 비동기 라이브러리에서
사용하는 객체에 대한 extension
function 생성
- (Flux.toList, Multi.toList,
CompletionStage.awaitSingle
은 실제와 달라요)
02. Coroutine 톺아보기
FSM 기반의 Coroutines ; extension function
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId)
.subscribe { user ->
cont.resumeWith(Result.success(user))
}
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.subscribe(LastItemSubscriber { address ->
cont.resumeWith(Result.success(address))
})
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.collectList()
.subscribe { products ->
cont.resumeWith(Result.success(products))
}
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.collect().asList()
.subscribe().with { stores ->
cont.resumeWith(Result.success(stores))
}
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).whenComplete { order, _ ->
cont.resumeWith(Result.success(order))
}
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
}
02. Coroutine 톺아보기
FSM 기반의 Coroutines ; extension function
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId).awaitSingle(cont)
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.awaitLast(cont)
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.toList(cont)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.toList(cont)
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).awaitSingle(cont)
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
}
02. Coroutine 톺아보기
FSM 기반의 Coroutines ; when 제거
when (cont.label) {
0 -> {
cont.label = 1
userRepository.findUserByIdAsMaybe(userId).awaitSingle(cont)
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
addressRepository.findAddressByUserAsPublisher(cont.buyer)
.awaitLast(cont)
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(cont.address)
productRepository.findAllProductsByIdsAsFlux(productIds)
.toList(cont)
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(cont.products.isNotEmpty())
storeRepository.findStoresByProductsAsMulti(cont.products)
.toList(cont)
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(cont.stores.isNotEmpty())
orderRepository.createOrderAsFuture(
cont.buyer, cont.products, cont.stores, cont.address
).awaitSingle(cont)
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
}
else -> throw IllegalAccessException()
}
02. Coroutine 톺아보기
FSM 기반의 Coroutines ; continuation 제거
when (cont.label) {
0 -> {
cont.label = 1
val buyer = userRepository.findUserByIdAsMaybe(userId)
.awaitSingle()
}
1 -> {
cont.label = 2
cont.buyer = (cont.result as Result<User>).getOrThrow()
val address = addressRepository.findAddressByUserAsPublisher(buyer)
.awaitLast()
}
2 -> {
cont.label = 3
cont.address = (cont.result as Result<Address>).getOrThrow()
checkValidRegion(address)
val products = productRepository.findAllProductsByIdsAsFlux(productIds)
.toList()
}
3 -> {
cont.label = 4
cont.products = (cont.result as Result<List<Product>>).getOrThrow()
check(products.isNotEmpty())
val stores = storeRepository.findStoresByProductsAsMulti(products)
.toList()
}
4 -> {
cont.label = 5
cont.stores = (cont.result as Result<List<Store>>).getOrThrow()
check(stores.isNotEmpty())
val order = orderRepository.createOrderAsFuture(
buyer, products, stores, address
).awaitSingle()
}
5 -> {
cont.order = (cont.result as Result<Order>).getOrThrow()
cont.completion.resumeWith(Result.success(cont.order))
return order
}
else -> throw IllegalAccessException()
}
02. Coroutine 톺아보기
Coroutines ; 최종 // 0 ->
val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()
// 1 ->
val address = addressRepository.findAddressByUserAsPublisher(buyer)
.awaitLast()
checkValidRegion(address)
// 2 ->
val products = productRepository.findAllProductsByIdsAsFlux(productIds)
.toList()
check(products.isNotEmpty())
// 3 ->
val stores = storeRepository.findStoresByProductsAsMulti(products)
.toList()
check(stores.isNotEmpty())
// 4 ->
val order = orderRepository.createOrderAsFuture(
buyer, products, stores, address
).awaitSingle()
return order
02. Coroutine 톺아보기
Coroutines ; 실행 @Test
fun `should return a createdOrder in coroutine`() = runBlocking {
// given
val userId = "user1"
val productIds = listOf("product1", "product2", "product3")
// when
val watch = StopWatch().also { it.start() }
val inputValues = CreateOrderCoroutineUseCase.InputValues(userId, productIds)
val createdOrder = createOrderUseCase.execute(inputValues)
watch.stop()
println("Time Elapsed: ${watch.time}ms")
// then
println(createdOrder)
}
다음회 예고
03
03. 다음회 예고
Async를 사용한 동시성 처리 suspend fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle()
// 2. 주소 조회 및 유효성 체크
val addressDeferred = CoroutineScope(Dispatchers.IO).async {
addressRepository.findAddressByUserAsPublisher(buyer)
.awaitLast()
}
// 3. 상품들 조회
val products =
productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList()
check(products.isNotEmpty())
// 4. 스토어 조회
val storesDeferred = CoroutineScope(Dispatchers.IO).async {
storeRepository.findStoresByProductsAsMulti(products).asFlow().toList()
}
val address = addressDeferred.await()
val stores = storesDeferred.await()
checkValidRegion(address)
check(stores.isNotEmpty())
// 5. 주문 생성
- CoroutineDispatcher
- 여러 Thread를 오고가며 로직을
처리 가능
03. 다음회 예고
try/catch를 이용한 에러 핸들링 suspend fun execute(inputValues: InputValues): Order {
val (userId, productIds) = inputValues
// 1. 구매자 조회
val buyer = try {
userRepository.findUserByIdAsMaybe(userId).awaitSingle()
} catch (e: Exception) {
throw NoSuchElementException("no such user")
}
// 2. 주소 조회 및 유효성 체크
val address = addressRepository.findAddressByUserAsPublisher(buyer)
.awaitLast()
checkValidRegion(address)
// 3. 상품들 조회
val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList()
check(products.isNotEmpty())
// 4. 스토어 조회
val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList()
check(stores.isNotEmpty())
// 5. 주문 생성
val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await()
return order
}
- try/catch를 통해서 일관성 있게
에러 핸들링 가능
03. 다음회 예고
그 외에
- Structured concurrency
- Channel
- Flow
QnA

More Related Content

What's hot

[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?
NAVER D2
 
Weblogic Server Overview Weblogic Scripting Tool
Weblogic Server Overview Weblogic Scripting ToolWeblogic Server Overview Weblogic Scripting Tool
Weblogic Server Overview Weblogic Scripting Tool
Gokhan Fazli Celik
 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
Jeevesh Pandey
 
초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기
OnGameServer
 
Nuxt.JS Introdruction
Nuxt.JS IntrodructionNuxt.JS Introdruction
Nuxt.JS Introdruction
David Ličen
 
Spring boot - an introduction
Spring boot - an introductionSpring boot - an introduction
Spring boot - an introduction
Jonathan Holloway
 
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
흥배 최
 
카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개
카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개
카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개
if kakao
 
이것이 레디스다.
이것이 레디스다.이것이 레디스다.
이것이 레디스다.
Kris Jeong
 
An introduction to Vue.js
An introduction to Vue.jsAn introduction to Vue.js
An introduction to Vue.js
Javier Lafora Rey
 
React js
React jsReact js
The Point of Vue - Intro to Vue.js
The Point of Vue - Intro to Vue.jsThe Point of Vue - Intro to Vue.js
The Point of Vue - Intro to Vue.js
Holly Schinsky
 
Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Wars of MySQL Cluster ( InnoDB Cluster VS Galera ) Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Mydbops
 
State management in react applications (Statecharts)
State management in react applications (Statecharts)State management in react applications (Statecharts)
State management in react applications (Statecharts)
Tomáš Drenčák
 
스프링5 웹플럭스와 테스트 전략
스프링5 웹플럭스와 테스트 전략스프링5 웹플럭스와 테스트 전략
스프링5 웹플럭스와 테스트 전략
if kakao
 
Javascript Module Patterns
Javascript Module PatternsJavascript Module Patterns
Javascript Module Patterns
Nicholas Jansma
 
How to Use JSON in MySQL Wrong
How to Use JSON in MySQL WrongHow to Use JSON in MySQL Wrong
How to Use JSON in MySQL Wrong
Karwin Software Solutions LLC
 
이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정
Arawn Park
 
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
NHN FORWARD
 

What's hot (20)

[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?
 
Weblogic Server Overview Weblogic Scripting Tool
Weblogic Server Overview Weblogic Scripting ToolWeblogic Server Overview Weblogic Scripting Tool
Weblogic Server Overview Weblogic Scripting Tool
 
Spring data jpa
Spring data jpaSpring data jpa
Spring data jpa
 
초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기초보자를 위한 분산 캐시 이야기
초보자를 위한 분산 캐시 이야기
 
Nuxt.JS Introdruction
Nuxt.JS IntrodructionNuxt.JS Introdruction
Nuxt.JS Introdruction
 
Spring boot - an introduction
Spring boot - an introductionSpring boot - an introduction
Spring boot - an introduction
 
Redis
RedisRedis
Redis
 
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
[KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
 
카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개
카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개
카카오 광고 플랫폼 MSA 적용 사례 및 API Gateway와 인증 구현에 대한 소개
 
이것이 레디스다.
이것이 레디스다.이것이 레디스다.
이것이 레디스다.
 
An introduction to Vue.js
An introduction to Vue.jsAn introduction to Vue.js
An introduction to Vue.js
 
React js
React jsReact js
React js
 
The Point of Vue - Intro to Vue.js
The Point of Vue - Intro to Vue.jsThe Point of Vue - Intro to Vue.js
The Point of Vue - Intro to Vue.js
 
Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Wars of MySQL Cluster ( InnoDB Cluster VS Galera ) Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
 
State management in react applications (Statecharts)
State management in react applications (Statecharts)State management in react applications (Statecharts)
State management in react applications (Statecharts)
 
스프링5 웹플럭스와 테스트 전략
스프링5 웹플럭스와 테스트 전략스프링5 웹플럭스와 테스트 전략
스프링5 웹플럭스와 테스트 전략
 
Javascript Module Patterns
Javascript Module PatternsJavascript Module Patterns
Javascript Module Patterns
 
How to Use JSON in MySQL Wrong
How to Use JSON in MySQL WrongHow to Use JSON in MySQL Wrong
How to Use JSON in MySQL Wrong
 
이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정이벤트 기반 분산 시스템을 향한 여정
이벤트 기반 분산 시스템을 향한 여정
 
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
 

Similar to Kotlin coroutines 톺아보기

Base tables for order to cash
Base tables for order to cashBase tables for order to cash
Base tables for order to cash
pranav kumar verma
 
CodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPigCodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPig
Dusan Zamurovic
 
Order to cash with tables
Order to cash with tablesOrder to cash with tables
Order to cash with tables
sharatit
 
Solid Software Design Principles
Solid Software Design PrinciplesSolid Software Design Principles
Solid Software Design Principles
Jon Kruger
 
Testing the waters of iOS
Testing the waters of iOSTesting the waters of iOS
Testing the waters of iOS
Kremizas Kostas
 
Building RESTful API
Building RESTful APIBuilding RESTful API
Building RESTful API
Anna Pietras
 
Rhino Mocks
Rhino MocksRhino Mocks
Rhino Mocks
Anand Kumar Rajana
 
MET CS 669 Database Design and Implementation for BusinessLab .docx
MET CS 669 Database Design and Implementation for BusinessLab .docxMET CS 669 Database Design and Implementation for BusinessLab .docx
MET CS 669 Database Design and Implementation for BusinessLab .docx
buffydtesurina
 
Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails
Mohit Jain
 
2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...
2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...
2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...
PHP Conference Argentina
 
10 PHP Snippets to Increase WooCommerce Sales
10 PHP Snippets to Increase WooCommerce Sales10 PHP Snippets to Increase WooCommerce Sales
10 PHP Snippets to Increase WooCommerce Sales
Rodolfo Melogli
 
RESTful services Design Lab
RESTful services Design LabRESTful services Design Lab
RESTful services Design Lab
Paulo Gandra de Sousa
 
The uniform interface is 42
The uniform interface is 42The uniform interface is 42
The uniform interface is 42
Yevhen Bobrov
 
MBL205 Monetizing Your App on Kindle Fire - AWS re: Invent 2012
MBL205 Monetizing Your App on Kindle Fire  - AWS re: Invent 2012MBL205 Monetizing Your App on Kindle Fire  - AWS re: Invent 2012
MBL205 Monetizing Your App on Kindle Fire - AWS re: Invent 2012
Amazon Web Services
 
Basic Unit Testing with Mockito
Basic Unit Testing with MockitoBasic Unit Testing with Mockito
Basic Unit Testing with Mockito
Alexander De Leon
 
Php mysql
Php mysqlPhp mysql
Php mysql
Manish Jain
 
Dependency injection - the right way
Dependency injection - the right wayDependency injection - the right way
Dependency injection - the right way
Thibaud Desodt
 
Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01
Jason Noble
 
Migrating One of the Most Popular eCommerce Platforms to MongoDB
Migrating One of the Most Popular eCommerce Platforms to MongoDBMigrating One of the Most Popular eCommerce Platforms to MongoDB
Migrating One of the Most Popular eCommerce Platforms to MongoDB
MongoDB
 
Migrating one of the most popular e commerce platforms to mongodb
Migrating one of the most popular e commerce platforms to mongodbMigrating one of the most popular e commerce platforms to mongodb
Migrating one of the most popular e commerce platforms to mongodb
MongoDB
 

Similar to Kotlin coroutines 톺아보기 (20)

Base tables for order to cash
Base tables for order to cashBase tables for order to cash
Base tables for order to cash
 
CodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPigCodingSerbia2014-JavaVSPig
CodingSerbia2014-JavaVSPig
 
Order to cash with tables
Order to cash with tablesOrder to cash with tables
Order to cash with tables
 
Solid Software Design Principles
Solid Software Design PrinciplesSolid Software Design Principles
Solid Software Design Principles
 
Testing the waters of iOS
Testing the waters of iOSTesting the waters of iOS
Testing the waters of iOS
 
Building RESTful API
Building RESTful APIBuilding RESTful API
Building RESTful API
 
Rhino Mocks
Rhino MocksRhino Mocks
Rhino Mocks
 
MET CS 669 Database Design and Implementation for BusinessLab .docx
MET CS 669 Database Design and Implementation for BusinessLab .docxMET CS 669 Database Design and Implementation for BusinessLab .docx
MET CS 669 Database Design and Implementation for BusinessLab .docx
 
Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails
 
2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...
2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...
2013 - Nate Abele: HTTP ALL THE THINGS: Simplificando aplicaciones respetando...
 
10 PHP Snippets to Increase WooCommerce Sales
10 PHP Snippets to Increase WooCommerce Sales10 PHP Snippets to Increase WooCommerce Sales
10 PHP Snippets to Increase WooCommerce Sales
 
RESTful services Design Lab
RESTful services Design LabRESTful services Design Lab
RESTful services Design Lab
 
The uniform interface is 42
The uniform interface is 42The uniform interface is 42
The uniform interface is 42
 
MBL205 Monetizing Your App on Kindle Fire - AWS re: Invent 2012
MBL205 Monetizing Your App on Kindle Fire  - AWS re: Invent 2012MBL205 Monetizing Your App on Kindle Fire  - AWS re: Invent 2012
MBL205 Monetizing Your App on Kindle Fire - AWS re: Invent 2012
 
Basic Unit Testing with Mockito
Basic Unit Testing with MockitoBasic Unit Testing with Mockito
Basic Unit Testing with Mockito
 
Php mysql
Php mysqlPhp mysql
Php mysql
 
Dependency injection - the right way
Dependency injection - the right wayDependency injection - the right way
Dependency injection - the right way
 
Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01
 
Migrating One of the Most Popular eCommerce Platforms to MongoDB
Migrating One of the Most Popular eCommerce Platforms to MongoDBMigrating One of the Most Popular eCommerce Platforms to MongoDB
Migrating One of the Most Popular eCommerce Platforms to MongoDB
 
Migrating one of the most popular e commerce platforms to mongodb
Migrating one of the most popular e commerce platforms to mongodbMigrating one of the most popular e commerce platforms to mongodb
Migrating one of the most popular e commerce platforms to mongodb
 

Recently uploaded

HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAUHCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
panagenda
 
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
Neo4j
 
Presentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of GermanyPresentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of Germany
innovationoecd
 
Infrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI modelsInfrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI models
Zilliz
 
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
Neo4j
 
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
Neo4j
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
Zilliz
 
20240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 202420240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 2024
Matthew Sinclair
 
GraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracyGraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracy
Tomaz Bratanic
 
20240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 202420240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 2024
Matthew Sinclair
 
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
SOFTTECHHUB
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
IndexBug
 
Artificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopmentArtificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopment
Octavian Nadolu
 
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
Edge AI and Vision Alliance
 
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
名前 です男
 
GenAI Pilot Implementation in the organizations
GenAI Pilot Implementation in the organizationsGenAI Pilot Implementation in the organizations
GenAI Pilot Implementation in the organizations
kumardaparthi1024
 
20240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 202420240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 2024
Matthew Sinclair
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
Safe Software
 
Mariano G Tinti - Decoding SpaceX
Mariano G Tinti - Decoding SpaceXMariano G Tinti - Decoding SpaceX
Mariano G Tinti - Decoding SpaceX
Mariano Tinti
 
Removing Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software FuzzingRemoving Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software Fuzzing
Aftab Hussain
 

Recently uploaded (20)

HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAUHCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
HCL Notes und Domino Lizenzkostenreduzierung in der Welt von DLAU
 
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
GraphSummit Singapore | Neo4j Product Vision & Roadmap - Q2 2024
 
Presentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of GermanyPresentation of the OECD Artificial Intelligence Review of Germany
Presentation of the OECD Artificial Intelligence Review of Germany
 
Infrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI modelsInfrastructure Challenges in Scaling RAG with Custom AI models
Infrastructure Challenges in Scaling RAG with Custom AI models
 
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
 
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
 
20240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 202420240609 QFM020 Irresponsible AI Reading List May 2024
20240609 QFM020 Irresponsible AI Reading List May 2024
 
GraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracyGraphRAG for Life Science to increase LLM accuracy
GraphRAG for Life Science to increase LLM accuracy
 
20240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 202420240605 QFM017 Machine Intelligence Reading List May 2024
20240605 QFM017 Machine Intelligence Reading List May 2024
 
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
Why You Should Replace Windows 11 with Nitrux Linux 3.5.0 for enhanced perfor...
 
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial IntelligenceAI 101: An Introduction to the Basics and Impact of Artificial Intelligence
AI 101: An Introduction to the Basics and Impact of Artificial Intelligence
 
Artificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopmentArtificial Intelligence for XMLDevelopment
Artificial Intelligence for XMLDevelopment
 
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
“Building and Scaling AI Applications with the Nx AI Manager,” a Presentation...
 
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
みなさんこんにちはこれ何文字まで入るの?40文字以下不可とか本当に意味わからないけどこれ限界文字数書いてないからマジでやばい文字数いけるんじゃないの?えこ...
 
GenAI Pilot Implementation in the organizations
GenAI Pilot Implementation in the organizationsGenAI Pilot Implementation in the organizations
GenAI Pilot Implementation in the organizations
 
20240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 202420240607 QFM018 Elixir Reading List May 2024
20240607 QFM018 Elixir Reading List May 2024
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
 
Mariano G Tinti - Decoding SpaceX
Mariano G Tinti - Decoding SpaceXMariano G Tinti - Decoding SpaceX
Mariano G Tinti - Decoding SpaceX
 
Removing Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software FuzzingRemoving Uninteresting Bytes in Software Fuzzing
Removing Uninteresting Bytes in Software Fuzzing
 

Kotlin coroutines 톺아보기

  • 1. Kotlin Coroutines 톺아보기 발 표 자 : 김태우(당근마켓 로컬 커머스 팀) 밋업자료 : 밋업코드 : https://github.com/gaaon/kotlin-coroutines-examples
  • 2. 00 비동기에 대한 고민 01 Coroutine 소개 02 Coroutine 톺아보기 03 다음회 예고 목차 목차
  • 3. - 로컬 커머스팀 백엔드 개발자 - 커머스에서 로컬의 가치를 찾는 팀 시작하기에 앞서
  • 5. - 한번에 이해하기 힘들다 - 추적이 어렵다 - 에러 핸들링이 어렵다 00. 비동기에 대한 고민 userRepository.findUserByIdAsMaybe(userId) .subscribe { buyer -> addressRepository.findAddressByUserAsPublisher(buyer) .subscribe(LastItemSubscriber { address -> checkValidRegion(address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> check(products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(products) .collect().asList() .subscribe().with { stores -> check(stores.isNotEmpty()) orderRepository.createOrderAsFuture( buyer, products, stores, address ).whenComplete { order, _ -> emitter.success(order) } } } }) } 동기 프로그래밍과 다르다
  • 6. - 어떻게 혼용해서 써야 할까? - 어떤 결과 타입을 반환해야 할까? - 또 다른 비동기 라이브러리가 추가되면? 00. 비동기에 대한 고민 다양한 비동기 라이브러리
  • 7. - 우수한 가독성 - 에러 핸들링 - 동시성 처리 - Flow - Channel 00. 비동기에 대한 고민 Coroutine이 해결사? suspend fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle() // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList() check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList() check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await() return order }
  • 9. - 구매자 정보 - 상품 정보 - 스토어 정보 - 배송지 01. Coroutine 소개 상품 주문
  • 11. - 구매자 조회 - 주소 조회 및 유효성 체크 - 상품 목록 조회 - 스토어 목록 조회 - 주문 생성 01. Coroutine 소개 주문 생성 동기 코드 fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdSync(userId) // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserSync(buyer).last() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsSync(productIds) check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsSync(products) check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderSync(buyer, products, stores, address) return order }
  • 13. - rxjava3의 Maybe - 0 .. 1, Error 01. Coroutine 소개 구매자 조회 import io.reactivex.rxjava3.core.Maybe interface UserAsyncRepository { fun findUserByIdAsMaybe(userId: String): Maybe<User> } class UserRxRepository : UserRepositoryBase(), UserAsyncRepository { override fun findUserByIdAsMaybe(userId: String): Maybe<User> { val user = prepareUser(userId) return Maybe.just(user) .delay(TIME_DELAY_MS, TimeUnit.MILLISECONDS) } }
  • 14. - jdk9의 Flow 사용 - item을 publish 하고 complete 이벤트로 flow 종료 01. Coroutine 소개 주소 조회 import java.util.concurrent.Flow interface AddressAsyncRepository { fun findAddressByUserAsPublisher(user: User): Flow.Publisher<Address> }
  • 15. - reactor의 Flux 사용 - 0 .. n, Error 01. Coroutine 소개 상품 조회 import reactor.core.publisher.Flux interface ProductAsyncRepository { fun findAllProductsByIdsAsFlux(productIds: List<String>): Flux<Product> }
  • 16. - mutiny의 Multi 사용 - 0 .. n, Error 01. Coroutine 소개 스토어 조회 import io.smallrye.mutiny.Multi interface StoreAsyncRepository { fun findStoresByProductsAsMulti(products: List<Product>): Multi<Store> }
  • 17. - jdk8의 CompletableFuture - complete 되는 시점에 결과 반환 01. Coroutine 소개 주문 생성 import java.util.concurrent.CompletableFuture interface OrderAsyncRepository { fun createOrderAsFuture( buyer: User, products: List<Product>, stores: List<Store>, address: Address, ): CompletableFuture<Order> }
  • 18. - subscribe는 결과를 얻은 시점에 주어진 subscriber (consumer)를 실행하는 일종의 callback - 반환값들이 아래에서 계속 필요해서 subscribe가 중첩 01. Coroutine 소개 subscribe hell fun execute(inputValues: InputValues): Mono<Order> { val (userId, productIds) = inputValues return Mono.create { emitter -> userRepository.findUserByIdAsMaybe(userId) .subscribe { buyer -> addressRepository.findAddressByUserAsPublisher(buyer) .subscribe(LastItemSubscriber { address -> checkValidRegion(address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> check(products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(products) .collect().asList() .subscribe().with { stores -> check(stores.isNotEmpty()) orderRepository.createOrderAsFuture( buyer, products, stores, address ).whenComplete { order, _ -> emitter.success(order) } } } }) } } }
  • 19. - 각각의 비동기 함수를 Reactor로 변경 - RxJava3Adapter - JdkFlowAdapter - Flux.collectList - Flux.from - Mono.fromFuture 01. Coroutine 소개 flatMap hell fun execute(inputValues: InputValues): Mono<Order> { val (userId, productIds) = inputValues return RxJava3Adapter.maybeToMono(userRepository.findUserByIdAsMaybe(userId)) .flatMap { buyer -> JdkFlowAdapter.flowPublisherToFlux( addressRepository.findAddressByUserAsPublisher(buyer)) .last() .flatMap { address -> checkValidRegion(address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .flatMap { products -> check(products.isNotEmpty()) Flux.from(storeRepository.findStoresByProductsAsMulti(products) .collectList() .flatMap { stores -> check(stores.isNotEmpty()) Mono.fromFuture( orderRepository.createOrderAsFuture( buyer, products, stores, address ) ) } } } } }
  • 21. - Maybe<T>.awaitSingle - Publisher<T>.awaitLast - Flow<T>.toList - CompletableFuture<T>.await 01. Coroutine 소개 Coroutine suspend fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle() // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList() check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList() check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await() return order }
  • 22. 01. Coroutine 소개 suspend fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle() // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList() check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList() check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await() return order } fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdSync(userId) // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserSync(buyer).last() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsSync(productIds) check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsSync(products) check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderSync(buyer, products, stores, address) return order } 동기코드 그리고 Coroutine
  • 23. 01. Coroutine 소개 Coroutine 실행 @Test fun `should return a createdOrder in coroutine`() = runBlocking { // given val userId = "user1" val productIds = listOf("product1", "product2", "product3") // when val watch = StopWatch().also { it.start() } val inputValues = CreateOrderCoroutineUseCase.InputValues(userId, productIds) val createdOrder = createOrderUseCase.execute(inputValues) watch.stop() println("Time Elapsed: ${watch.time}ms") // then println(createdOrder) } - runBlocking은 동기코드에서 coroutine을 실행할 수 있게 bridge 역할
  • 26. - 경량화된 쓰레드? - 특정 지점에서 정지했다가 재개할 수 있는 쓰레드? 02. Coroutine 톺아보기 Coroutine? suspend fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle() // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserAsPublisher(buyer).awaitLast() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList() check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList() check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await() return order }
  • 27. - Finite State Machine 기반의 재귀 함수로 변환 kotlin 컴파일러가 suspend가 붙은 함수에 추가적인 코드를 추가 - Continuation 인자를 타겟 함수에 추가하고 Continuation 구현체를 생성 - 타겟 함수 내의 모든 suspend 함수에 생성한 continuation 객체를 패스 - 코드를 분리해서 switch case 안에 넣고 label을 이용해서 state를 변경 02. Coroutine 톺아보기 Kotlin Compiler
  • 28. - execute 함수가 실행되면 재귀 호출을 이용해서 스스로(execute 함수)를 실행하면서 state를 변경 - state가 최종에 도달하면 값을 caller에 반환 02. Coroutine 톺아보기 FSM 기반의 재귀 함수 재귀호출 재귀호출 재귀호출 재귀호출 complete
  • 29. FSM 기반의 동기 코드로
  • 30. - SharedData를 통해서 여러 가지 context를 저장 - label은 state machine의 현재 state 값 - 이전 state에서 찾은 값들을 buyer, address, products, stores, order에 저장 - resumeWith로 재귀 호출을 하고 결과를 result에 저장 - 인자의 sharedData가 null이라면 생성하고 아니면 있는 sharedData를 사용 02. Coroutine 톺아보기 FSM 기반의 동기 코드 class SharedData { var label: Int = 0 lateinit var result: Any lateinit var buyer: User lateinit var address: Address lateinit var products: List<Product> lateinit var stores: List<Store> lateinit var order: Order lateinit var resumeWith: (result: Any) -> Order } fun execute( inputValues: InputValues, sharedData: SharedData? = null, ): Order { val (userId, productIds) = inputValues val that = this val shared = sharedData ?: SharedData().apply { this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) } }
  • 31. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() }
  • 32. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; state 0 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() } this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) }
  • 33. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; state 1 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() } this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) }
  • 34. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; state 2 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() } this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) }
  • 35. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; state 3 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() } this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) }
  • 36. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; state 4 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() } this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) }
  • 37. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; state 5 return when (shared.label) { 0 -> { shared.label = 1 userRepository.findUserByIdSync(userId) .let { user -> shared.resumeWith(user) } } 1 -> { shared.label = 2 shared.buyer = shared.result as User addressRepository.findAddressByUserSync(shared.buyer).last() .let { address -> shared.resumeWith(address) } } 2 -> { shared.label = 3 shared.address = shared.result as Address checkValidRegion(shared.address) productRepository.findAllProductsByIdsSync(productIds) .let { products -> shared.resumeWith(products) } } 3 -> { shared.label = 4 shared.products = shared.result as List<Product> check(shared.products.isNotEmpty()) storeRepository.findStoresByProductsSync(shared.products) .let { stores -> shared.resumeWith(stores) } } 4 -> { shared.label = 5 shared.stores = shared.result as List<Store> check(shared.stores.isNotEmpty()) orderRepository.createOrderSync( shared.buyer, shared.products, shared.stores, shared.address ).let { order -> shared.resumeWith(order) } } 5 -> { shared.order = shared.result as Order shared.order } else -> throw IllegalAccessException() } this.resumeWith = fun (result: Any): Order { this.result = result return that.execute(inputValues, this) }
  • 38. 02. Coroutine 톺아보기 FSM 기반의 동기 코드 ; 실행 @Test fun `should return a createdOrder in sync with state machine`() { // given val userId = "user1" val productIds = listOf("product1", "product2", "product3") // when val watch = StopWatch().also { it.start() } val inputValues = CreateOrderSyncStateMachineUseCase.InputValues( userId, productIds) val createdOrder = createOrderUseCase.execute(inputValues) watch.stop() println("Time Elapsed: ${watch.time}ms") // then println(createdOrder) } - execute를 실행할때, 두번째 인자를 넘기지 않아서 default value인 null로 제공
  • 40. - SharedDataContinuation를 통해서 여러 가지 context를 저장 - label은 state machine의 현재 state 값 - 이전 state에서 찾은 값들을 buyer, address, products, stores, order에 저장 - resumeWith로 재귀 호출을 하여 결과를 result에 저장 - 인자의 sharedData가 SharedDataContinuation 타입이 아니라면 생성 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 class SharedDataContinuation( // 실제로 continuation 구현체는 method 안에 val completion: Continuation<Any>, ) : Continuation<Any> { var label: Int = 0 lateinit var result: Any lateinit var buyer: User lateinit var address: Address lateinit var products: List<Product> lateinit var stores: List<Store> lateinit var order: Order lateinit var resume: () -> Unit override val context: CoroutineContext = completion.context override fun resumeWith(result: Result<Any>) { this.result = result this.resume() } } fun execute(inputValues: InputValues, completion: Continuation<Any>) { val (userId, productIds) = inputValues val that = this val cont = completion as? SharedDataContinuation ?: SharedDataContinuation(completion).apply { resume = fun() { // recursive self that.execute(inputValues, this)
  • 41. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 class SharedDataContinuation( val completion: Continuation<Any>, ) : Continuation<Any> { var label: Int = 0 lateinit var result: Any lateinit var buyer: User lateinit var address: Address lateinit var products: List<Product> lateinit var stores: List<Store> lateinit var order: Order lateinit var resume: () -> Unit override val context: CoroutineContext = completion.context override fun resumeWith(result: Result<Any>) { this.result = result this.resume() } } fun execute(inputValues: InputValues, completion: Continuation<Any>) { val (userId, productIds) = inputValues val that = this val cont = completion as? SharedDataContinuation ?: SharedDataContinuation(completion).apply { resume = fun() { // recursive self that.execute(inputValues, this) } public interface Continuation<in T> { /** * The context of the coroutine that corresponds to this continuation. */ public val context: CoroutineContext /** * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the * return value of the last suspension point. */ public fun resumeWith(result: Result<T>) }
  • 42. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; 실행 - testContinuation을 생성해서 execute 함수에 주입 @Test fun `should return a createdOrder in async with state machine`() { // given val userId = "user1" val productIds = listOf("product1", "product2", "product3") // when val watch = StopWatch().also { it.start() } val lock = CountDownLatch(1) val testContinuation = object : Continuation<Any> { override val context = EmptyCoroutineContext override fun resumeWith(result: Result<Any>) { watch.stop() lock.countDown() println("Time Elapsed: ${watch.time}ms") println(result.getOrThrow()) } } val inputValues = CreateOrderAsyncStateMachine3UseCase.InputValues(userId, productIds) createOrderUseCase.execute(inputValues, testContinuation) // then lock.await(3000, TimeUnit.MILLISECONDS) }
  • 43. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 44. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; state 0 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } override fun resumeWith(result: Result<Any>) { this.result = result that.execute(inputValues, this) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 45. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; state 1 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } override fun resumeWith(result: Result<Any>) { this.result = result that.execute(inputValues, this) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 46. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; state 2 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } override fun resumeWith(result: Result<Any>) { this.result = result that.execute(inputValues, this) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 47. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; state 3 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } override fun resumeWith(result: Result<Any>) { this.result = result that.execute(inputValues, this) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 48. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; state 4 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } override fun resumeWith(result: Result<Any>) { this.result = result that.execute(inputValues, this) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 49. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; state 5 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } override fun resumeWith(result: Result<Any>) { this.result = result that.execute(inputValues, this) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 50. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; 최종 class SharedDataContinuation( val completion: Continuation<Any>, ) : Continuation<Any> { var label: Int = 0 lateinit var result: Any lateinit var buyer: User lateinit var address: Address lateinit var products: List<Product> lateinit var stores: List<Store> lateinit var order: Order lateinit var resume: () -> Unit override val context: CoroutineContext = completion.context override fun resumeWith(result: Result<Any>) { this.result = result this.resume() } } fun execute(inputValues: InputValues, completion: Continuation<Any>) { val (userId, productIds) = inputValues val that = this val cont = completion as? SharedDataContinuation ?: SharedDataContinuation(completion).apply { resume = fun() { // recursive self that.execute(inputValues, this) } - completion은 외부에서 주입하는 continuation 기반의 객체
  • 51. 02. Coroutine 톺아보기 FSM 기반의 비동기 코드 ; 실행 - testContinuation을 생성해서 execute 함수에 주입 @Test fun `should return a createdOrder in async with state machine`() { // given val userId = "user1" val productIds = listOf("product1", "product2", "product3") // when val watch = StopWatch().also { it.start() } val lock = CountDownLatch(1) val testContinuation = object : Continuation<Any> { override val context = EmptyCoroutineContext override fun resumeWith(result: Result<Any>) { watch.stop() lock.countDown() println("Time Elapsed: ${watch.time}ms") println(result.getOrThrow()) } } val inputValues = CreateOrderAsyncStateMachine3UseCase.InputValues(userId, productIds) createOrderUseCase.execute(inputValues, testContinuation) // then lock.await(3000, TimeUnit.MILLISECONDS) }
  • 53. 02. Coroutine 톺아보기 FSM 기반의 Coroutines ; 시작 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException()
  • 54. 02. Coroutine 톺아보기 FSM 기반의 Coroutines ; extension function fun <T: Any> Maybe<T>.awaitSingle(cont: Continuation<Any>) { this.subscribe { user -> cont.resumeWith(Result.success(user)) } } fun <T: Any> Flow.Publisher<T>.awaitLast(cont: Continuation<Any>) { this.subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } fun <T: Any> Flux<T>.toList(cont: Continuation<Any>) { this.collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } fun <T: Any> Multi<T>.toList(cont: Continuation<Any>) { this.collect() .asList() .subscribeAsCompletionStage() .whenComplete { stores, _ -> cont.resumeWith(Result.success(stores)) } } fun <T: Any> CompletionStage<T>.awaitSingle(cont: - 각각의 비동기 라이브러리에서 사용하는 객체에 대한 extension function 생성 - (Flux.toList, Multi.toList, CompletionStage.awaitSingle 은 실제와 달라요)
  • 55. 02. Coroutine 톺아보기 FSM 기반의 Coroutines ; extension function when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId) .subscribe { user -> cont.resumeWith(Result.success(user)) } } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .subscribe(LastItemSubscriber { address -> cont.resumeWith(Result.success(address)) }) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .collectList() .subscribe { products -> cont.resumeWith(Result.success(products)) } } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .collect().asList() .subscribe().with { stores -> cont.resumeWith(Result.success(stores)) } } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).whenComplete { order, _ -> cont.resumeWith(Result.success(order)) } } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException() }
  • 56. 02. Coroutine 톺아보기 FSM 기반의 Coroutines ; extension function when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId).awaitSingle(cont) } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .awaitLast(cont) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .toList(cont) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .toList(cont) } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).awaitSingle(cont) } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException() }
  • 57. 02. Coroutine 톺아보기 FSM 기반의 Coroutines ; when 제거 when (cont.label) { 0 -> { cont.label = 1 userRepository.findUserByIdAsMaybe(userId).awaitSingle(cont) } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() addressRepository.findAddressByUserAsPublisher(cont.buyer) .awaitLast(cont) } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(cont.address) productRepository.findAllProductsByIdsAsFlux(productIds) .toList(cont) } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(cont.products.isNotEmpty()) storeRepository.findStoresByProductsAsMulti(cont.products) .toList(cont) } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(cont.stores.isNotEmpty()) orderRepository.createOrderAsFuture( cont.buyer, cont.products, cont.stores, cont.address ).awaitSingle(cont) } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) } else -> throw IllegalAccessException() }
  • 58. 02. Coroutine 톺아보기 FSM 기반의 Coroutines ; continuation 제거 when (cont.label) { 0 -> { cont.label = 1 val buyer = userRepository.findUserByIdAsMaybe(userId) .awaitSingle() } 1 -> { cont.label = 2 cont.buyer = (cont.result as Result<User>).getOrThrow() val address = addressRepository.findAddressByUserAsPublisher(buyer) .awaitLast() } 2 -> { cont.label = 3 cont.address = (cont.result as Result<Address>).getOrThrow() checkValidRegion(address) val products = productRepository.findAllProductsByIdsAsFlux(productIds) .toList() } 3 -> { cont.label = 4 cont.products = (cont.result as Result<List<Product>>).getOrThrow() check(products.isNotEmpty()) val stores = storeRepository.findStoresByProductsAsMulti(products) .toList() } 4 -> { cont.label = 5 cont.stores = (cont.result as Result<List<Store>>).getOrThrow() check(stores.isNotEmpty()) val order = orderRepository.createOrderAsFuture( buyer, products, stores, address ).awaitSingle() } 5 -> { cont.order = (cont.result as Result<Order>).getOrThrow() cont.completion.resumeWith(Result.success(cont.order)) return order } else -> throw IllegalAccessException() }
  • 59. 02. Coroutine 톺아보기 Coroutines ; 최종 // 0 -> val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle() // 1 -> val address = addressRepository.findAddressByUserAsPublisher(buyer) .awaitLast() checkValidRegion(address) // 2 -> val products = productRepository.findAllProductsByIdsAsFlux(productIds) .toList() check(products.isNotEmpty()) // 3 -> val stores = storeRepository.findStoresByProductsAsMulti(products) .toList() check(stores.isNotEmpty()) // 4 -> val order = orderRepository.createOrderAsFuture( buyer, products, stores, address ).awaitSingle() return order
  • 60. 02. Coroutine 톺아보기 Coroutines ; 실행 @Test fun `should return a createdOrder in coroutine`() = runBlocking { // given val userId = "user1" val productIds = listOf("product1", "product2", "product3") // when val watch = StopWatch().also { it.start() } val inputValues = CreateOrderCoroutineUseCase.InputValues(userId, productIds) val createdOrder = createOrderUseCase.execute(inputValues) watch.stop() println("Time Elapsed: ${watch.time}ms") // then println(createdOrder) }
  • 62. 03. 다음회 예고 Async를 사용한 동시성 처리 suspend fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = userRepository.findUserByIdAsMaybe(userId).awaitSingle() // 2. 주소 조회 및 유효성 체크 val addressDeferred = CoroutineScope(Dispatchers.IO).async { addressRepository.findAddressByUserAsPublisher(buyer) .awaitLast() } // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList() check(products.isNotEmpty()) // 4. 스토어 조회 val storesDeferred = CoroutineScope(Dispatchers.IO).async { storeRepository.findStoresByProductsAsMulti(products).asFlow().toList() } val address = addressDeferred.await() val stores = storesDeferred.await() checkValidRegion(address) check(stores.isNotEmpty()) // 5. 주문 생성 - CoroutineDispatcher - 여러 Thread를 오고가며 로직을 처리 가능
  • 63. 03. 다음회 예고 try/catch를 이용한 에러 핸들링 suspend fun execute(inputValues: InputValues): Order { val (userId, productIds) = inputValues // 1. 구매자 조회 val buyer = try { userRepository.findUserByIdAsMaybe(userId).awaitSingle() } catch (e: Exception) { throw NoSuchElementException("no such user") } // 2. 주소 조회 및 유효성 체크 val address = addressRepository.findAddressByUserAsPublisher(buyer) .awaitLast() checkValidRegion(address) // 3. 상품들 조회 val products = productRepository.findAllProductsByIdsAsFlux(productIds).asFlow().toList() check(products.isNotEmpty()) // 4. 스토어 조회 val stores = storeRepository.findStoresByProductsAsMulti(products).asFlow().toList() check(stores.isNotEmpty()) // 5. 주문 생성 val order = orderRepository.createOrderAsFuture(buyer, products, stores, address).await() return order } - try/catch를 통해서 일관성 있게 에러 핸들링 가능
  • 64. 03. 다음회 예고 그 외에 - Structured concurrency - Channel - Flow
  • 65. QnA