기본 패턴

MonoFlux 는 파이프라인 조립만으로는 실행되어 결과를 반환하지 않는다. 어느 지점에서든 해당 객체들에 subscribe() 를 호출해야 실제 실행이 일어난다.

자세한건 Reactive Streams 를 참고

SpringWebFlux에서는 개발자가 직접 subscribe 를 호출하고 후속처리를 하지 않는게 원칙이다.

// 안티패턴
mono.subscribe(result -> {
    // 처리
});

// 그냥 반환
public Mono<Result> handle() {
    return service.doSomething(); // subscribe는 프레임워크가 함
}

기본적으로 WebFlux가 알아서 구독하기 때문에 직접 호출하면 프레임워크의 스케쥴링/에러처리 흐름에서 벗어남.

어떻게 WebFlux는 자동으로 Mono나 Flux를 구독할까? WebFlux의 핵심 엔진은 Netty임. 이 기반의 이벤트 루프로 동작하고, HTTP요청이 들어오면 맞는 Handler를 찾아서 실행한뒤, 핸들러가 반환한 Mono/Flux를 직접 구독하는 것 까지 처리해준다.

WebFlux패턴에서 핵심은 조립과 실행이 나뉘어 있다는 것임. 개발자는 조립만 하고 프레임워크는 실행한다.

subscribe 를 직접 호출해도 되는 경우는 반환값이 없는 케이스들이다. fire-and-forget 타입

// fire-and-forget
public Mono<Void> process(String id) {
    return mainService.process(id)
        .doOnSuccess(r -> 
            sideEffect.run(r)
                .subscribeOn(Schedulers.boundedElastic())
                .subscribe(
                    null,
                    err -> log.error("사이드 이펙트 실패", err)
                )
        );
}

오버로딩

Mono기준 오버로딩

subscribe 에 전달되는 람다들은 모두 스트림이 이미 종료된 후에 실행될 콜백들이다. 이들은 데이터스트림안에 있는 데이터에 접근 불가함.

// 1. 아무것도 안 넘김
mono.subscribe();

// 2. 성공 값만
mono.subscribe(
    value -> log.info("성공: {}", value)
);

// 3. 성공 + 에러
mono.subscribe(
    value -> log.info("성공: {}", value),
    error -> log.error("실패", error)
);

// 4. 성공 + 에러 + 완료
mono.subscribe(
    value    -> log.info("성공: {}", value),
    error    -> log.error("실패", error),
    ()       -> log.info("완료")
);

// 5. 성공 + 에러 + 완료 + 구독 시점
mono.subscribe(
    value        -> log.info("성공: {}", value),
    error        -> log.error("실패", error),
    ()           -> log.info("완료"),
    subscription -> subscription.request(1) // 백프레셔 제어
);

인자로 넘긴 람다의 호출 시점

subscribe() 호출
  ↓
onSubscribe  → 구독 시작 시점 (Subscription 받음)
  ↓
onNext       → 값이 emit될 때마다 (Mono는 최대 1번)
  ↓
onComplete   → 정상 완료 시
또는
onError      → 에러 발생 시