우당탕탕
Spring WebFlux 적용해보면서 헷갈렸던 개념들 직접 해결한 후기 본문
처음에 Spring WebFlux를 적용하면서 생각보다 헷갈린 부분이 많았어요. 특히 기존의 스프링 MVC와 다른 리액티브 프로그래밍 패러다임 때문에 몇 가지 개념이 명확하지 않아서 삽질을 꽤 했거든요.
이 글에서는 제가 직접 경험하면서 가장 자주 고민하고 검색했던 질문들을 중심으로, 하나씩 해결 과정을 공유하려고 해요. 그래서 WebFlux를 처음 적용하는 분들도 이 글만 보면 주요 개념과 문제 해결법을 한 번에 잡을 수 있을 거예요.
개발 환경 / 버전 정보
저는 Java 17에 Spring Boot 3.1.4 버전을 사용했고,
Reactive 라이브러리는 Spring WebFlux와 Project Reactor를 썼어요. 데이터베이스는 R2DBC 기반으로 MySQL 연동했습니다.
Spring WebFlux 적용해보면서 헷갈렸던 개념들 관련 정보
왜 WebFlux가 필요한지, 기존 MVC와 뭘 다르게 봐야 할까?
사실 이 부분이 가장 헷갈릴 수밖에 없었는데요, WebFlux는 논블로킹(Non-blocking) 리액티브 프로그래밍 모델이에요. 기존 Spring MVC는 요청 하나당 쓰레드를 하나 차지해서 처리하는데, WebFlux는 요청의 흐름을 이벤트와 데이터 스트림으로 처리해서 리소스 사용이 효율적이라는 점이 달라요.
제가 WebFlux를 적용하면서 가장 크게 느낀 건, 컨트롤러 메서드에서 무조건 Mono나 Flux 같은 리액티브 타입을 써야 한다는 점이었어요.
Spring WebFlux 적용해보면서 헷갈렸던 개념들 관련 정보
실제로 가장 많이 물어봤던 질문, Q&A로 풀어봤어요
Q1. WebFlux에서 꼭 Mono와 Flux만 써야 하나요? 그냥 객체 반환하면 안 되나요?
A. 네, 사실 WebFlux 컨트롤러에서는 리액티브 타입인 Mono나 Flux를 반드시 반환해야 해요. 일반 객체 반환 시, Spring MVC처럼 동작하지 않고 빈 결과가 나올 수도 있어요. Mono는 0~1개, Flux는 0~N개의 데이터를 비동기 스트림으로 나타냅니다.
자주 다음과 같은 코드를 보게 되는데, 이렇게 쓰면 동작하지 않아요:
@GetMapping("/hello")
public String hello() {
return "Hello WebFlux!"; // 그냥 String 반환은 안 되어요
}
반드시 이렇게 써야 한다는 점 기억하세요!
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Hello WebFlux!");
}
이게 기본 중 기본인데, 의외로 이걸 모르고 MVC 익숙한 대로 쓰다가 삽질하는 경우가 많더라고요.
Q2. WebFlux에서 데이터베이스 접근은 어떻게 다를까요? JpaRepository 대신 뭐 써야 하나요?
A. JpaRepository는 동기적 API라 WebFlux와 잘 안 맞아요. 저는 R2DBC 리포지토리를 같이 적용했는데요. 이건 리액티브 방식으로 DB에 접근할 수 있게 해줘서 Mono와 Flux를 반환합니다.
예를 들어 아래처럼 사용했어요:
public interface UserRepository extends ReactiveCrudRepository<User, String> {
Flux<User> findByLastName(String lastName);
}
// 서비스에서
public Flux<User> getUsersByLastName(String lastName) {
return userRepository.findByLastName(lastName);
}
JPA의 모든 기능을 쓸 수는 없지만, 간단한 CRUD나 쿼리는 WebFlux 환경에 맞게 돌아가서 좋아요.
Q3. WebFlux에서 예외 처리 어떻게 해야 하나요? 기존 @ControllerAdvice 그대로 쓰면 될까요?
A. 기본적으로 @ControllerAdvice와 @ExceptionHandler를 쓸 수 있지만, 리액티브 환경에서 예외가 발생한 Mono/Flux 스트림 내부를 올바르게 처리하는 것이 중요해요.
예를 들어 서비스에서 예외가 발생하면 Mono의 onErrorResume 같은 연산자를 붙여서 대체 동작을 정의할 수 있죠:
public Mono<User> getUser(String id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException("User not found")))
.onErrorResume(e -> {
// 예외 발생 시 fallback 처리
if (e instanceof UserNotFoundException) {
return Mono.empty(); // 빈 Mono로 대체
}
return Mono.error(e);
});
}
이렇게 스트림을 잘 관리해야 에러가 비동기 환경에서 깔끔하게 처리돼요.
Q4. WebFlux는 블로킹 코드가 있으면 어떻게 되나요?
A. WebFlux에서는 블로킹 코드를 최대한 피해야 해요. 예를 들어 쓰레드 슬래시싱이 안 되서 자원 낭비나 시스템 부하가 발생할 수 있거든요. 저도 R2DBC를 적용하면서 JPA 동기 호출이 섞이는 실수를 했는데, 즉시 성능 저하가 발생했어요.
만약 꼭 블로킹 코드를 써야 한다면, 별도의 스레드 풀에서 처리하거나 Schedulers.boundedElastic() 같은 스케줄러를 써서 격리해야 해요.
Mono.fromCallable(() -> blockingMethod())
.subscribeOn(Schedulers.boundedElastic())
.subscribe(result -> {
// 결과 처리
});
이게 없으면 WebFlux의 장점을 다 못 살리는 거니까 조심하세요.
Q5. WebFlux에서 테스트는 어떻게 해야 할까요? 기존 MVC 테스트와 달라요?
A. 테스트도 리액티브 타입을 고려해야 해서, StepVerifier라는 Reactor 전용 테스트 도구를 많이 사용해요. Mono나 Flux가 내보내는 이벤트를 시퀀스 단위로 검증할 수 있거든요.
예를 들어 Controller 테스트 시 다음처럼 쓰죠:
@Test
void helloReturnsMono() {
Mono<String> result = webFluxController.hello();
StepVerifier.create(result)
.expectNext("Hello WebFlux!")
.verifyComplete();
}
기존 MockMvc 같은 것보다 더 리액티브 스트림에 특화된 검증이 가능해요.
Q6. WebClient랑 WebFlux는 어떤 관계인가요? 언제 WebClient를 써야 하는지 궁금해요.
A. WebClient는 WebFlux 환경에서 HTTP 비동기 호출을 할 때 주로 쓰는 클라이언트예요. RestTemplate는 블로킹이라 WebFlux 환경에는 적합하지 않죠.
제가 API 연동할 때 WebClient를 썼는데, 다음처럼 비동기로 요청-응답을 처리할 수 있어요:
WebClient client = WebClient.create("https://api.example.com");
Mono<String> responseMono = client.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);
responseMono.subscribe(System.out::println);
이게 기존 동기 API 호출보다 훨씬 효율적이더라고요.
Q7. WebFlux에서 동시성(concurrency) 처리는 어떻게 다뤄야 할까요?
A. WebFlux는 이벤트 루프 기반 non-blocking 서버라 동시성 접근 문제가 적다고 생각할 수 있는데, 공유 상태가 있으면 동기화는 여전히 필요해요.
예를 들어, Flux 내에서 어떤 변수에 값을 누적하거나 멀티스레드 환경에서 수정한다면 동기화가 필요하고, Reactor의 여러 연산자(예: publishOn, subscribeOn)를 이용해 스케줄링을 조절해야 해요.
동시성 문제는 기존 스프링과는 달리, 리액티브 스트림 연산자 이해와 스케줄러 설정이 관건인데, 제가 이 부분에서 한참 고민했거든요.
Spring WebFlux 적용해보면서 헷갈렸던 개념들 관련 정보
여기서 더 헷갈렸던 부분들
제가 겪은 문제 중 하나는 WebFlux에서 block() 메서드를 너무 습관적으로 쓴 거예요. 실제로 이렇게 하면 논블로킹 장점 다 날아가고 데드락이나 응답 지연 문제가 생기더라고요.
그래서 되도록이면 스트림 끝까지 비동기 흐름을 유지하는 방향으로 코드를 고친 게 큰 도움이 됐어요.
또한, WebFlux 핸들러 함수 내부에서 리액티브 연산자를 잘못써서 subscribe()를 중복 호출하는 실수를 했는데, 이게 의외로 멀티플 구독 발생과 자원 누수를 불러와서 꼭 주의해야 할 부분이었어요.
추가로 알면 좋은 팁
WebFlux를 쓸 때 API 문서화에는 springdoc-openapi 라이브러리의 WebFlux 지원 기능을 사용하는 게 편리해요. 기존 Swagger에서 자꾸 호환 문제 생기던 부분이 개선됐거든요.
그리고, 로그를 남길 때는 Reactor의 Hooks.onOperatorDebug()를 활성화하면 어디서 에러가 났는지 추적하기 쉬워져서 많이 썼어요.
마무리하며
Spring WebFlux는 기존 스프링 MVC와는 확실히 다른 개념과 구조라 처음에 적응이 쉽지 않지만, 문법이나 API가 아니라 비동기 스트림 처리 개념을 이해하는 게 핵심이에요.
무엇보다 Mono와 Flux가 리액티브 체인의 기본 단위인 거, 그리고 블로킹 코드를 반드시 피해야 한다는 점을 꼭 기억하세요. 제가 겪었던 시행착오와 질문들을 보며 앞으로 WebFlux 적용 시 도움이 됐으면 좋겠네요.
'Tech > Spring' 카테고리의 다른 글
| Spring Boot에서 Querydsl 도입, 2026년 바뀐 점과 실제 적용기 (0) | 2026.10.09 |
|---|---|
| JPA N+1 문제, 저도 처음 겪고 이렇게 해결했어요 (0) | 2026.10.02 |
| Spring Boot JWT 로그인 구현하다 삽질한 것들 공유합니다 (0) | 2026.09.10 |
| Spring Batch 처음 써보면서 겪은 시행착오와 꼭 챙긴 체크리스트 (0) | 2026.09.07 |
| Spring WebFlux 적용해보면서 헷갈렸던 개념들 직접 경험한 체크리스트 (0) | 2026.08.26 |
