Spring Boot Reactive(WebFlux)로 비동기 API 만들기
Spring WebFlux의 핵심 개념과 실습 예제를 통해 Mono/Flux 기반 비동기 흐름과 Reactor 연동, 간단한 구현 구조를 설명
목차
소개
Spring Boot Reactive(WebFlux)는 요청을 비동기로 처리하는 프레임워크다. 전통적인 서블릿 기반과 다르게 논블로킹 방식으로 동시성을 효율적으로 다룬다. 이 글에서는 개념 설명과 함께 spring webflux 예제 코드를 통해 비동기 API를 만드는 과정을 단계별로 살펴본다.
핵심 개념
Reactive Programming 기본
Reactive는 데이터의 흐름과 변경을 비동기적으로 처리한다. 핵심은 Publisher와 Subscriber로 요약된다. Reactive Streams 규격을 따르며, backpressure를 지원한다.
Mono와 Flux
Reactor 라이브러리는 Mono와 Flux를 제공한다. Mono는 0 또는 1개의 값을 비동기로 전달한다. Flux는 0개 이상, 스트림 형태의 데이터를 전달한다. 이 두 타입으로 비동기 파이프라인을 구성한다.
프로젝트 설정
Spring Boot에서 WebFlux를 사용하려면 의존성을 추가한다. Maven 예제는 다음과 같다.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
</dependencies>
간단한 예제
데이터 모델과 서비스
우선 간단한 서비스에서 Flux와 Mono를 반환하도록 만든다. 실제로는 Reactive 저장소(R2DBC, Mongo Reactive 등)를 사용하지만, 예제에서는 메모리 스트림을 사용한다.
package com.example.demo.service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.List;
public class ItemService {
private final List<String> items = List.of("apple", "banana", "cherry");
public Flux<String> findAll() {
// 예제용으로 500ms 간격을 둔 스트림 반환
return Flux.fromIterable(items).delayElements(Duration.ofMillis(500));
}
public Mono<String> findOne(int index) {
if (index < 0 || index >= items.size()) {
return Mono.empty();
}
return Mono.just(items.get(index));
}
}
컨트롤러
컨트롤러는 반환 타입으로 Mono나 Flux를 사용한다. WebFlux는 반환값을 구독해 비동기 응답을 처리한다.
package com.example.demo.controller;
import com.example.demo.service.ItemService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
public class ItemController {
private final ItemService service = new ItemService();
@GetMapping("/items")
public Flux<String> getAll() {
return service.findAll();
}
@GetMapping("/items/{id}")
public Mono<String> getOne(@PathVariable int id) {
return service.findOne(id);
}
}
실행 및 테스트
애플리케이션을 실행한 뒤 curl로 테스트한다. Flux는 스트리밍 응답으로 처리된다.
# 전체 리스트
curl -v http://localhost:8080/items
# 단건
curl -v http://localhost:8080/items/1
브라우저나 HTTP 클라이언트에서 SSE(Server-Sent Events)를 사용하면 스트리밍 데이터를 실시간으로 소비하기 쉽다. 또한 웹소켓 연동도 가능하다.
실무 고려사항
- 블로킹 코드 주의: 데이터베이스나 외부 API 호출이 블로킹이라면 별도 스레드 풀에서 처리해야 한다.
- Backpressure: 클라이언트가 소비 속도를 따라오지 못하면 리액티브 파이프라인에서 조절 필요.
- 테스트: WebTestClient를 사용하면 WebFlux 컨트롤러를 손쉽게 검증 가능.
- 연동: 기존 MVC와 동시에 사용할 수 있으나, 아키텍처 결정 시 일관성을 고려한다.
간단한 최적화 팁
- 작업이 많은 블로킹 연산은 Scheduler.boundedElastic() 같은 별도 스케줄러로 이동.
- Flux에서 map 대신 flatMap을 사용할 때 병렬성 조정에 유의.
- 로그와 모니터링으로 요청 지연과 스레드 상황을 파악.
정리
spring boot reactive webflux는 높은 동시성 요구와 낮은 대기 시간을 목표로 할 때 유용하다. reactive programming spring boot의 기본 개념인 Mono와 Flux를 이해하면 비동기 API 설계가 쉬워진다. 위 예제는 시작점이며, 실제 환경에서는 리액티브 데이터베이스와 적절한 스케줄링 전략을 함께 고려해야 한다.