본문 바로가기
Develop/Reactor+Coroutine

[Reactor] zip vs zipWith vs zipWhen

by 연로그 2024. 4. 7.
반응형

💡 zip()

img: projectreactor 공식문서

  • 여러개의 Publisher를 인자로 넘기고, 이 모든 Publisher들의 결과를 한 묶음으로 조회할 수 있다.
  • Publisher를 최대 8개까지 넘길 수 있다.
  • 예외나 빈값이 발견되면 즉시 에러/완료 처리된다.
Flux<User> userFluxFromStringFlux(
    Flux<String> usernameFlux,
    Flux<String> firstNameFlux,
    Flux<String> lastNameFlux
) {
    return Flux.zip(usernameFlux, firstNameFlux, lastNameFlux)
        .map(it -> new User(it.getT1(),it.getT2(),it.getT3()));
        // 아래와 같이 각 결과에 이름을 붙일 수 있다.
        // .map((username, firstName, lastName) -> new User(username, firstName, lastName));
}

 

💥 zip()의 주의점

zip()은 빈값이 발견되면 즉시 완료 처리 된다. 예를 들어 아래와 같이 lastNameFlux와 firstNameFlux를 조합한 결과를 콘솔에 출력하고자 한다. 참고로 lastNameFlux에는 5개의 item이 있고, firstNameFlux는 2개의 item이 있다. 콘솔에 어떤 값이 출력될까?

public void exampleZip() {
    var lastNameFlux = Flux.just("권", "연", "가", "나", "다");
    var firstNameFlux = Flux.just("시연", "로그");

    Flux.zip(lastNameFlux, firstNameFlux)
        .subscribe(it -> System.out.println(it.getT1() + it.getT2()));
}

 

정답은 권시연, 연로그 두 값만 출력된다. 

 

zip은 인자로 주어진 모든 source(예제의 lastNameFlux, firstNameFlux)에서 하나의 요소를 방출할 때까지 기다렸다가 이 요소들을 묶음으로 제공한다. 처음에는 "권"과 "시연"이 한 묶음이 되었을 것이고, 그 다음은 "연"과 "로그"가 한 묶음이 되었다. 하지만 "가"와 한 묶음이 될 요소는 더이상 존재하지 않는다. 따라서 더이상 묶음이 제공되지 않고, "가", "나", "다" 요소들에 대한 어떤 처리를 기대할 수 없다. 만약 빈값이더라도 계속 실행될 것이라고 기대하고 코드를 짠다면 예상치 못한 결과를 가져올 수 있다.

가, 나, 다는 묶일 요소가 존재하지 않는다.

 

 

💡 zipWith()

img: projectreactor 공식문서

  • Mono/Flux에서 방출된 값을 이용해 인자로 주어진 Publisher 소스와 결합한다.
  • 예외나 빈값이 발견되면 즉시 에러/완료 처리된다.
private void zipWithExample() {
    // number1 + number2의 결과 구하는 예제
    var number1 = Flux.just(1, 2, 3);
    var number2 = Flux.just(1, 2, 3);

    number1.zipWith(number2, Integer::sum)
        .subscribe(System.out::println);
}

 

 

💡 zipWhen()

img: projectreactor 공식문서

  • Mono에서 방출된 값을 이용해 인자로 주어진 Publisher 소스와 결합한다.
    (zipWith은 Mono와 Flux에서, zipWhen은 Mono에서만 호출 가능!)
// 이 예제는 zipWith과 동일한 예제를 보여주기 위해 작성되었을 뿐, 이해하기 쉬운 예제는 아니다. 
// 이 예제가 이해가 되지 않는다면 '🍯 zipWhen() 사용 팁' 부분의 예제를 살펴보자.
private void zipWhenExample() {
    // number1 + number2의 결과 구하는 예제
    var number1 = Mono.just(1);
    var number2 = Mono.just(2);

    number1.zipWhen(it -> number2, Integer::sum)
        .subscribe(System.out::println);
}

 

 

🍯 zipWhen() 사용 팁

zipWhen()은 flatMap() 대신 유용하게 쓸 수 있다. 코드를 보고 가독성이 좋다고 느끼는 방식을 선택하자.

/**
 * 1. username을 이용해 회원 데이터를 조회한다.
 * 2. 1에서 조회한 회원 데이터를 이용해 찜 데이터를 조회한다.
 * 3. 1, 2에서 조회한 회원/찜 데이터를 이용해 FavoriteResponse를 생성한다.
*/
private void zipWhenExample(String username) {
    // flatMap을 사용한다면?
    userRepository.findByUsername(username)
        .flatMap(user ->
            favoriteRepository.findByUserId(user.getId())
                .flatMap(favorite -> Mono.just(toFavoriteResponse(user, favorite)))
        )
    ;

    // zipWhen을 사용한다면?
    userRepository.findByUsername(username)
        .zipWhen(user -> favoriteRepository.findByUserId(user.getId()))
        .map(it -> toFavoriteResponse(it.getT1(), it.getT2()))
    ;

    // zipWhen의 두번째 인자로 넘길 수도 있다!
    userRepository.findByUsername(username)
        .zipWhen(user -> 
            favoriteRepository.findByUserId(user.getId()), 
            ZipTest::toFavoriteResponse
        );
}

private FavoriteResponse toFavoriteResponse(User user, Favorite favorite) { ... }

 

 

💥 zipWith(), zipWhen()의 차이점

zipWith()과 zipWhen()은 언뜻 보면 zipWhen()은 Mono에서만 사용 가능하다는 점을 제외하고 동일해보인다. 하지만 둘은 동작 방식에서 차이가 있다.

 

zipWith()은 현재 Publisher와 파라미터로 전달된 Publisher의 각 요소를 결합해 묶음(Tuple2)를 생성한다. 두 Publisher의 요소들을 순차적으로 결합할 때 사용할 수 있다.

private void test(String username, Long favoriteId) {
    userRepository.findByUsername(username)
        .zipWith(favoriteRepository.findById(favoriteId))
        .map(it -> toFavoriteResponse(it.getT1(), it.getT2()));
}

private FavoriteResponse toFavoriteResponse(User user, Favorite favorite) { ... }

 

zipWhen()은 현재 Publisher의 각 요소를 사용해 새로운 Publisher를 동적으로 생성한다. 그리고 현재 Publisher와 새로 만들어진 Publisher의 각 요소를 결합해 새로운 묶음(Tuple2)를 생성한다. 각 요소에 대해 비동기 작업을 수행하고, 그 결과를 원래 요소와 결합할 때 사용할 수 있다.

private void test(String username) {
    userRepository.findByUsername(username)
        // user 정보를 이용해 데이터를 조회해야 한다면???
        .zipWhen(user -> favoriteRepository.findByUserId(user.getId))
        .map(it -> toFavoriteResponse(it.getT1(), it.getT2()));
}

private FavoriteResponse toFavoriteResponse(User user, Favorite favorite) { ... }

 

 

💥 zipWith(), zipWhen()의 주의점

Mono<Void>나 빈 Mono는 아무런 데이터도 방출하지 않는다. 이런 케이스에서 zipWith()와 zipWhen()을 호출하고자 하면 호출되지 않는다. zipWith()과 zipWhen()은 각 source로부터 최소 하나의 요소를 받아야 그 요소들을 결합할 수 있음을 기억하자.

private static void emptyAndZipWith() {
    Mono.empty()
        .zipWith(Mono.just(1)) // 호출되지 않는다.
        .subscribe(System.out::println);
}

 


참고

반응형