zip

스태틱 메서드로 여러 Mono와 Flux를 조합할때 사용한다. 왜 조합하는가? 여러 mono, flux를 실행해서 그 결과를 받아오기까지 순차로 실행하는 대신, 여러 mono,flux를 병렬로 실행해 실행속도를 최적화하는게 목적임

// 독립적인 두 API를 병렬로 호출할 때
// 순차 실행 (느림)
val user = userService.find(1L).block()
val order = orderService.find(1L).block()

// 병렬 실행 (빠름)
Mono.zip(
    userService.find(1L),
    orderService.find(1L)
).map { (user, order) -> ... }

mono 예시

val userMono = userService.find(1L)
val orderMono = orderService.findByUserId(1L)

// 둘 다 완료되면 Tuple2로 결합
Mono.zip(userMono, orderMono)
    .map { (user, order) ->
        UserOrderResponse(user, order)
    }

flux예시

// Flux는 같은 인덱스끼리 결합
Flux.zip(
    Flux.just(1, 2, 3),
    Flux.just("a", "b", "c")
)
// → (1, "a"), (2, "b"), (3, "c")
// 짧은 쪽 기준으로 완료

zipwith

파이프라인을 구성할때, 다른 mono나 flux를 가져와 합치고 싶을때 사용한다.

// 파이프라인 안에서 다른 Mono와 결합
userService.find(1L)
    .zipWith(orderService.findByUserId(1L))
    .map { (user, order) ->
        UserOrderResponse(user, order)
    }

Tuple

한편, zip은 두 개 이상의 Mono를 동시에 실행해 합치는데, 그 두 Mono가 같은 타입이지 않을 확률이 더 높다.

이럴때 두 값을 하나로 묶어서 전달하기 위한 컨테이너 클래스로 리액터에서는 Tuple 을 사용한다. n-tuble이냐에 따라 Tuplen 의 이름이 붙는다.

// zip은 두 Mono를 동시에 실행
// 결과가 각각 다른 타입일 수 있음
Mono<User>  +  Mono<Order>
  ↓
Mono<Tuple2<User, Order>>  // 둘을 하나로 묶음

사용예시는 다음과 같다.

.map { tuple ->
    tuple.t1  // 첫 번째
    tuple.t2  // 두 번째
}

// 구조 분해도 가능
.map { (user, order) ->
    UserOrderResponse(user, order)
}