Spring mvc vs Spring web-flux

 

 1. Spring MVC vs WebFlux — 큰 그림

공통점

  • 둘 다 Spring Framework로 웹 애플리케이션을 만들 때 사용함.
  • 둘 다 HTTP 요청을 받고, 응답을 보냄.
  • 둘 다 Controller 또는 비슷한 구조로 라우팅 처리 가능.

MVC란?

  • Model-View-Controller 패턴을 따름.
  • 요청을 받으면 쓰레드 하나가 요청 전담해서 처리함.

흐름

  1. 사용자가 브라우저로 요청을 보냄
  2. DispatcherServlet이 요청을 받음
  3. @Controller에서 처리
  4. 데이터를 가공해서 응답 반환

예시

@RestController
class HelloController {

    @GetMapping("/hello")
    fun hello(): String {
        return "Hello, MVC"
    }
}

 

WebFlux란?

Spring WebFlux

  • Reactive Programming 기반으로 동작
  • 내부적으로 Netty 같은 논블로킹 서버 사용
  • 하나의 쓰레드가 여러 요청을 동시에 처리할 수 있음
  • 코루틴, 리액터(Flux, Mono) 등을 사용함

흐름

  1. 사용자가 요청 보냄
  2. WebFlux가 논블로킹 방식으로 처리
  3. 비동기적으로 데이터 처리
  4. 결과가 준비되면 응답

예시 (코루틴 기반)

@RestController
class HelloHandler {

    @GetMapping("/hello")
    suspend fun hello(): String {
        delay(1000) // 논블로킹 지연
        return "Hello, WebFlux"
    }
}

 

그럼 "Reactive"란?

Reactive = 반응형

  • 시스템이 이벤트에 반응해서 동작하는 것
  • 예: 요청이 들어오면 바로 처리하지 않고, 데이터가 준비되면 반응해서 응답함
  • 대표 키워드:
    • 비동기 (Asynchronous)
    • 논블로킹 (Non-blocking)
    • 이벤트 스트림 (Event stream)

MVC vs WebFlux 비교표

  Spring MVC Spring WebFlux
프로그래밍 모델 동기(블로킹) 비동기(논블로킹, 리액티브)
쓰레드 사용 요청마다 하나씩 하나의 쓰레드로 여러 요청 처리
기본 서버 Tomcat, Jetty Netty, Undertow
응답 처리 즉시 응답 (동기) 데이터 준비되면 응답 (비동기)
개발 난이도 낮음 (단순함) 높음 (추상화 많음)

 

요약

  • Spring MVC: 쓰레드 하나가 요청 하나를 처리 (전통적, 쉬움)
  • Spring WebFlux: 쓰레드 하나가 여러 요청을 처리 (리액티브, 효율적)
  • WebFlux는 서버 리소스를 효율적으로 쓰고, 고성능이 필요한 곳에 적합

Reactive Programming은 실제로 어떤 식으로 동작하나?

MVC 방식WebFlux 리액티브 방식의 차이를 코드로 비교해서 이해해보자.

클라이언트가 /hello API를 호출했을 때, 서버가 3초 후에 "Hello"를 응답하도록 처리

1. Spring MVC 방식 (동기 / 블로킹)

@RestController
class MvcController {

    @GetMapping("/hello-mvc")
    fun hello(): String {
        Thread.sleep(3000) // 요청이 들어오면, 3초 동안 현재 쓰레드를 멈춤 (블로킹)
        return "Hello from MVC"
    }
}

 

  • 요청이 들어오면 서버 쓰레드 하나가 sleep(3초) 함
  • 그 동안은 다른 요청을 처리할 수 없음
  • 1000명의 사용자가 동시에 요청하면 1000개의 쓰레드가 필요함 → 서버 과부하

2. WebFlux 방식 (논블로킹 / 리액티브)

@RestController
class WebFluxController {

    @GetMapping("/hello-flux")
    suspend fun hello(): String {
        delay(3000) // 3초 대기하지만 쓰레드를 점유하지 않음 (논블로킹)
        return "Hello from WebFlux"
    }
}
  • delay(3000)는 코루틴이 잠깐 멈추는 것처럼 보이지만, 쓰레드를 점유하지 않음
  • 같은 쓰레드로 여러 요청을 처리 가능
  • 1000명의 요청도 몇 개 쓰레드로 처리가 가능 → 고성능
요청 수 MVC (블로킹) WebFlux (논블로킹)
1 O O
100 O O
1000 쓰레드 부족 발생 가능 처리 가능 (코루틴 + 논블로킹 덕분)

 

delay()는 논블로킹, Thread.sleep()은 블로킹 이런 논블로킹 흐름을 가능하게 해주는 게 바로 Reactive Programming

 


WebFlux의 핵심 구조인 RouterFunction, ServerRequest, ServerHttpRequest

목표

  • WebFlux에서 MVC의 @RestController 없이 API를 만드는 방법 이해
  • 그 안에서 ServerHttpRequest가 왜 등장하는지 알기

WebFlux의 함수형 스타일 구성

구성 요소 역할 설명

RouterFunction 요청 경로(URI)를 핸들러 함수에 연결
HandlerFunction 실제 요청 처리 (Controller 대신)
ServerRequest 요청 정보 (body, query, header 등) 추상화
ServerHttpRequest 아주 하위 레벨의 HTTP 요청 정보

코드 예제 — "Hello WebFlux" API 만들기

1. RouterFunction

@Configuration
class HelloRouter {

    @Bean
    fun route(helloHandler: HelloHandler): RouterFunction<ServerResponse> {
        return coRouter {
            GET("/hello", helloHandler::handleHello)
        }
    }
}

2. HandlerFunction

@Component
class HelloHandler {

    suspend fun handleHello(request: ServerRequest): ServerResponse {
        val name = request.queryParam("name").orElse("World")
        return ServerResponse.ok().bodyValueAndAwait("Hello, $name")
    }
}

여기서 ServerRequest는 뭘까?

  • queryParam(), bodyToMono(), headers(), pathVariable() 등 다양한 요청 정보에 접근할 수 있게 해줌
  • ServerHttpRequest는 내부적으로 ServerRequest가 사용하는 하위 객체

ServerHttpRequest 예제 — 직접 사용해보기

@Component
class HelloHandler {

    suspend fun handleHello(request: ServerRequest): ServerResponse {
        val httpRequest: ServerHttpRequest = request.exchange().request
        val userAgent = httpRequest.headers.getFirst("User-Agent")
        val clientIp = httpRequest.remoteAddress?.address?.hostAddress

        val message = "User-Agent: $userAgent, IP: $clientIp"
        return ServerResponse.ok().bodyValueAndAwait(message)
    }
}

왜 굳이 ServerHttpRequest를 쓰는가?

  • ServerRequest는 추상화가 많이 되어 있음
  • ServerHttpRequest는 헤더, 경로, 쿠키, IP 정보 등 HTTP 수준의 저수준 정보를 직접 다룰 수 있음

 다시 정리

객체 설명 언제 쓰는가

ServerRequest WebFlux용 추상화된 요청 객체 기본적인 query, body, header 처리
ServerHttpRequest 실제 HTTP 요청 정보 객체 IP, 전체 URI, 헤더 직접 접근이 필요할 때

결론

  • MVC에서는 @GetMapping으로 끝났지만,
  • WebFlux에서는 RouterFunction으로 경로 설정하고, HandlerFunction에서 직접 처리
  • 이 구조 덕분에 더 유연한 처리가 가능하지만, 개념적으로 더 어렵게 느껴짐
  • ServerHttpRequest는 request.exchange().request를 통해 접근 가능