문제 발견
개발 중 이상한 현상을 발견했다. Mono 내부에서 예외가 발생했는데, onErrorResume
이 동작하지 않는 것이었다.
문제 재현
기본 테스트 코드
Mono<List<String>> getNameList() {
throw new RuntimeException();
}
테스트 코드:
@Test
void test() {
getNameList().map(nameList -> {
System.out.println(nameList);
return nameList;
})
.flatMapMany(Flux::fromIterable)
.collectList()
.onErrorResume(e -> {
System.out.println("error");
return Mono.just(List.of("error"));
})
.block();
}
// 결과: java.lang.RuntimeException (onErrorResume 동작하지 않음)
문제 분석:
- Mono 생성 시점에서 바로 예외가 발생하면 Reactive Stream이 시작되기 전에 예외가 던져진다
- 따라서
onErrorResume
같은 에러 핸들링 연산자가 작동하지 않는다
정상 동작 확인
Mono.error를 사용한 경우
Mono<List<String>> getNameList() {
return Mono.error(new RuntimeException("error"));
}
// 결과: 정상적으로 "error" 출력
결과 분석:
Mono.error()
를 사용하면onErrorResume
이 정상적으로 동작한다- 이는 예외가 Reactive Stream 내에서 처리되기 때문이다
해결 방법
방법 1: try-catch로 감싸서 Mono.error 반환
Mono<List<String>> getNameList() {
try {
throw new RuntimeException();
} catch (Exception e) {
return Mono.error(e);
}
}
// 결과: 정상적으로 "error" 출력
장점: 명시적이고 이해하기 쉽다
단점: 매번 try-catch를 작성해야 한다
방법 2: Mono.defer()로 지연 실행
메서드는 그대로 두고, 호출하는 쪽에서 defer()
를 사용한다:
Mono<List<String>> getNameList() {
throw new RuntimeException();
}
@Test
void test() {
Mono.defer(() -> getNameList())
.map(nameList -> {
System.out.println(nameList);
return nameList;
})
.flatMapMany(Flux::fromIterable)
.collectList()
.onErrorResume(e -> {
System.out.println("error");
return Mono.just(List.of("error"));
})
.block();
}
// 결과: 정상적으로 "error" 출력
왜 defer()가 동작할까?
Mono.defer()
는 지연 실행(lazy evaluation) 을 제공한다:
- 구독 시점에 실행:
defer()
안의 코드는 실제로 구독이 일어날 때까지 실행되지 않는다 - 예외 캐치: 구독 시점에 발생한 예외는 Reactive Stream 내에서 처리된다
- 에러 시그널: 예외가 발생하면 자동으로
onError
시그널로 변환된다
결론
문제의 핵심:
- Mono 생성 시점의 즉시 예외는 Reactive Stream 밖에서 발생한다
- 따라서 에러 핸들링 연산자들이 동작하지 않는다
권장 해결방법:
- 가능한 한
Mono.error()
사용 - 불가피한 경우
Mono.defer()
활용 - 복잡한 로직은 try-catch로 명시적 처리
Reactive Programming에서는 예외도 스트림의 일부로 다뤄져야 한다는 점을 기억하자!
💬 댓글