Contents
see ListSpring WebFlux란?
Spring WebFlux는 Spring 5에서 도입된 리액티브 웹 프레임워크입니다. 논블로킹(Non-blocking) I/O를 기반으로 적은 스레드로 많은 동시 요청을 처리할 수 있어, 높은 동시성이 필요한 시스템에 적합합니다.
언제 사용하나요?
- 대용량 스트리밍 데이터 처리 (실시간 피드, 채팅)
- 마이크로서비스 간 비동기 통신
- I/O 바운드 작업이 많은 API 서버
- MSA 환경에서 다수의 외부 API 호출
MVC vs WebFlux 비교
| 항목 | Spring MVC | Spring WebFlux |
|---|---|---|
| I/O 모델 | 블로킹 | 논블로킹 |
| 스레드 | 요청당 1스레드 | 이벤트 루프 |
| 반환타입 | Object | Mono/Flux |
| 서버 | Tomcat | Netty |
기본 의존성
<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 애플리케이션과 완전 호환되지 않음