Spring 프레임워크에는 크게 두 가지 웹 프로그래밍 모델이 있습니다. Spring MVC와 Spring WebFlux입니다. 이 두 모델 간의 가장 큰 차이점은 비동기 처리 방식입니다.
전통적인 Spring MVC 모델은 Servlet 기반의 동기/블로킹 방식으로 동작합니다. 즉, 클라이언트의 요청이 들어오면 스레드가 blocking되어 해당 요청을 처리할 때까지 기다려야 합니다. 이로 인해 동시 사용자 증가 시 리소스 사용량이 늘어나는 문제가 있습니다.
반면, Spring WebFlux는 Reactor 기반의 비동기/논 블로킹 방식으로 동작합니다. 요청을 받으면 즉시 반환하고, 백그라운드에서 비동기적으로 데이터를 처리합니다. 이를 통해 적은 수의 스레드로도 높은 처리량을 달성할 수 있습니다.
외부 API를 호출하는 간단한 앱을 만든다고 가정해 봅시다. 전통적인 Spring MVC 방식과 Spring WebFlux 방식으로 구현한 코드는 다음과 같습니다
// Spring MVC (RestTemplate)
@GetMapping("/products")
public List<Product> getProducts() {
List<Product> list = restClient.get()
.uri("/demo01/products")
.retrieve()
.body(new ParameterizedTypeReference<List<Product>>() {});
log.info("received response: {}", list);
return list;
}
// Spring WebFlux (WebClient)
@GetMapping("/products")
public Flux<Product> getProducts() {
return webClient.get()
.uri("/demo01/products")
.retrieve()
.bodyToFlux(Product.class)
.doOnNext(product -> log.info("received: {}", product));
}
/demo01/products는 1초에 한번씩,10개의 Product의 정보를 생성하는 기능을 가지고 있습니다.
MVC
동기적인 방식의 mvc 모델은 모든 데이터를 한 번에 받아옵니다. 1초마다 생성되는 Product 정보를 전부 기다렸다가 List로 한꺼번에 반환합니다. 따라서 이 메서드는 모든 데이터가 준비될 때까지 블로킹됩니다.
WebFlux
이에반헤 WebFlux를 이용한 모델은 데이터를 스트리밍 방식으로 받아옵니다. 1초마다 생성되는 Product 정보를 매초마다 계속 처리할 수 있습니다. Flux를 통해 데이터가 준비되는 대로 비동기적으로 전달됩니다.
MVC: 요청이 완료될 때까지 스레드가 블로킹됩니다.
WebFlux: 논블로킹 방식으로 작동하여 적은 수의 스레드로 많은 요청을 처리할 수 있습니다.
Spring MVC
@GetMapping("/products")
public ResponseEntity<List<Product>> getProducts() {
try {
List<Product> list = restClient.get()
.uri("/demo01/products")
.retrieve()
.body(new ParameterizedTypeReference<List<Product>>() {});
return ResponseEntity.ok(list);
} catch (RestClientException e) {
log.error("Error fetching products", e);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
}
}
이 방식에서는 try-catch 블록을 사용하여 예외를 잡아내고 적절한 오류 응답을 반환해야 합니다. 또한 서버가 중간에 다운되면 데이터는 소실됩니다.
Spring WebFlux
@GetMapping("/products")
public Flux<Product> getProducts() {
return webClient.get()
.uri("/demo01/products")
.retrieve()
.bodyToFlux(Product.class)
.doOnNext(product -> log.info("received: {}", product))
.onErrorResume(e -> {
log.error("Error fetching products", e);
return Flux.empty();
});
}
WebFlux에서는 onErrorResume, onErrorComplete 등을 사용하여 오류를 쉽게 처리할 수 있습니다. 오류가 발생하면 빈 Flux를 반환하거나, 대체 데이터를 제공하는 등의 방식으로 유연하게 오류를 처리할 수 있습니다.
WebFlux의 방식이 더 유연하고 선언적인 오류 처리를 가능하게 합니다. 또한, 스트리밍 방식으로 데이터를 처리하기 때문에 서버가 중간에 다운되더라도 이미 받은 데이터는 처리할 수 있습니다.