Spring WebFlux란?


Spring WebFlux는 Spring 5에서 도입된 리액티브 웹 프레임워크입니다. 논블로킹(Non-blocking) I/O를 기반으로 적은 스레드로 많은 동시 요청을 처리할 수 있어, 높은 동시성이 필요한 시스템에 적합합니다.



언제 사용하나요?



  • 대용량 스트리밍 데이터 처리 (실시간 피드, 채팅)

  • 마이크로서비스 간 비동기 통신

  • I/O 바운드 작업이 많은 API 서버

  • MSA 환경에서 다수의 외부 API 호출



MVC vs WebFlux 비교








항목Spring MVCSpring WebFlux
I/O 모델블로킹논블로킹
스레드요청당 1스레드이벤트 루프
반환타입ObjectMono/Flux
서버TomcatNetty


기본 의존성


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>


Mono와 Flux 이해하기


// Mono: 0 또는 1개의 결과
Mono<User> user = userRepository.findById(id);

// Flux: 0 ~ N개의 결과 (스트림)
Flux<User> users = userRepository.findAll();

// 생성 방법
Mono<String> mono = Mono.just("Hello");
Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5);
Flux<Integer> range = Flux.range(1, 10);


리액티브 컨트롤러


@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

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

@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamUsers() {
return userService.findAll()
.delayElements(Duration.ofMillis(500));
}

@PostMapping
public Mono<User> createUser(@RequestBody Mono<User> user) {
return user.flatMap(userService::save);
}
}


WebClient - 리액티브 HTTP 클라이언트


@Service
public class ExternalApiService {

private final WebClient webClient;

public ExternalApiService(WebClient.Builder builder) {
this.webClient = builder
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE)
.build();
}

public Mono<ApiResponse> callApi(String path) {
return webClient.get()
.uri(path)
.retrieve()
.onStatus(HttpStatus::is4xxClientError,
response -> Mono.error(new ClientException()))
.bodyToMono(ApiResponse.class)
.timeout(Duration.ofSeconds(5))
.retry(3);
}

// 여러 API 동시 호출
public Mono<CombinedResult> callMultipleApis() {
Mono<A> apiA = callApiA();
Mono<B> apiB = callApiB();

return Mono.zip(apiA, apiB, CombinedResult::new);
}
}


에러 처리


Mono<User> user = userRepository.findById(id)
.switchIfEmpty(Mono.error(new NotFoundException()))
.onErrorResume(e -> {
log.error("Error: ", e);
return Mono.empty();
})
.onErrorReturn(new User()); // 기본값 반환


R2DBC - 리액티브 DB 접근


// 의존성
// spring-boot-starter-data-r2dbc
// r2dbc-postgresql

@Repository
public interface UserRepository
extends ReactiveCrudRepository<User, Long> {

Flux<User> findByStatus(String status);

@Query("SELECT * FROM users WHERE age > :age")
Flux<User> findOlderThan(int age);
}


주의사항



  • 블로킹 코드(JDBC, RestTemplate)와 혼용 금지

  • CPU 바운드 작업에는 부적합

  • 디버깅이 상대적으로 어려움

  • 기존 MVC 애플리케이션과 완전 호환되지 않음