Spring WebFlux의 Mono

2025. 5. 20. 22:15Back-End/Spring-Boot

반응형

Spring WebFlux를 처음 시작하면 가장 먼저 마주치는 게 바로 Mono와 Flux다.
이 중 Mono는 "0 또는 1개의 데이터를 비동기적으로 처리하는 Publisher"다.

한 줄 요약: Mono는 '1개의 데이터 또는 아무 것도 없는' 비동기 데이터 그릇이다.

 

왜 Mono를 써야 할까?

전통적인 Spring MVC에서는 메서드가 값을 리턴하면, 그 값은 동기적으로 바로 반환된다.
하지만 WebFlux는 Reactive Streams 기반으로 동작하기 때문에, Mono를 통해 데이터를 "나중에", "비동기로", "필요할 때" 처리한다.

@GetMapping("/user/{id}")
public Mono<User> getUser(@PathVariable String id) {
    return userRepository.findById(id); // Mono<User>
}

위 코드는 사용자 조회 요청에 대해 Mono<User>를 반환한다.
즉, 클라이언트는 응답이 "지금 당장" 오는 게 아니라, "언제든 값이 생기면" 받게 된다.


Mono 사용 시나리오

  • DB에서 하나의 row만 조회할 때 (findById)
  • 외부 API에서 단일 응답을 받을 때
  • 캐시에 값을 조회하거나, 없으면 비어있는 응답을 처리할 때

Mono 생성하기

1. Mono.just(value)

값이 있는 Mono 생성

Mono<String> mono = Mono.just("hello");

2. Mono.empty()

아무 값도 없는 Mono

Mono<String> emptyMono = Mono.empty();

3. Mono.error(Throwable)

에러를 담은 Mono

Mono<String> errorMono = Mono.error(new RuntimeException("Something went wrong"));

자주 쓰는 Mono 연산자

1. map(Function)

Mono 안의 값을 가공할 때 사용

Mono<String> upperMono = Mono.just("hello")
    .map(String::toUpperCase); // "HELLO"

2. flatMap(Function)

내부에서 Mono를 반환하는 비동기 호출을 체이닝할 때 사용

Mono<User> userMono = Mono.just("123")
    .flatMap(userRepository::findById); // Mono<User>

 

map은 값을 바꾸고,
flatMap은 다른 Mono를 반환하는 함수를 이어붙일 때 쓴다.

3. switchIfEmpty(Mono)

값이 없을 때 대체 Mono를 반환

userRepository.findById("notfound")
    .switchIfEmpty(Mono.just(new User("default")));

4. doOnNext(), doOnError(), doOnSuccess()

사이드 이펙트를 걸어두는 용도 (로깅 등)

Mono.just("data")
    .doOnNext(data -> log.info("값: " + data))
    .doOnError(e -> log.error("에러 발생", e));

예제 코드 - 사용자 정보 업데이트 API

@PutMapping("/user/{id}")
public Mono<ResponseEntity<String>> updateUser(@PathVariable String id,
                                               @RequestBody Mono<UserUpdateRequest> requestMono) {
    return requestMono
        .flatMap(request -> userRepository.findById(id)
            .switchIfEmpty(Mono.error(new UserNotFoundException()))
            .flatMap(user -> {
                user.setName(request.getName());
                return userRepository.save(user);
            }))
        .map(updatedUser -> ResponseEntity.ok("업데이트 완료"))
        .onErrorResume(e -> {
            if (e instanceof UserNotFoundException) {
                return Mono.just(ResponseEntity.status(404).body("사용자를 찾을 수 없습니다."));
            }
            return Mono.just(ResponseEntity.status(500).body("서버 에러"));
        });
}

이 코드 흐름은 다음과 같다

  1. requestMono를 통해 요청 바디를 Mono로 받는다.
  2. flatMap으로 내부 요청 값을 꺼내고, DB 조회
  3. 사용자가 없으면 switchIfEmpty로 에러 발생
  4. 값이 있다면 수정 → 저장
  5. 성공/에러에 따라 응답 다르게 반환

Mono vs Flux

구분 Mono Flux

설명 0 또는 1개의 데이터 0개 이상의 데이터
예시 사용자 정보 사용자 목록, 실시간 데이터 스트림

마무리

Mono는 처음에는 어색할 수 있지만, 실제 비동기 처리에서는 매우 강력한 도구다.
flatMap, switchIfEmpty, onErrorResume 같은 연산자만 잘 익혀도 대부분의 흐름은 처리할 수 있다.

핵심은 Mono는 “나중에 값이 생길 수 있음”을 전제로 한다는 것.
즉, 기다리지 말고, 흐름을 이어가라.

반응형