Spring Webflux

Jindolph·2024년 8월 6일

Spring MVC 와 Spring Webflux 비교

이번 포스트에서는, Spring MVC 에 익숙해진 개발자들에게 Webflux에 대한 간단한 소개와 비교 등을 진행하려고 합니다.

구성도에서, Repository 는 제외시켰습니다.

MVC 의 기본 개념

  1. Model-View-Controller (MVC):

    • MVC 패턴을 사용하여 애플리케이션의 비즈니스 로직과 사용자 인터페이스를 분리합니다.
    • Model: 애플리케이션의 데이터와 비즈니스 로직을 포함합니다.
    • View: 데이터를 표시하는 사용자 인터페이스를 담당합니다.
    • Controller: 사용자 입력을 처리하고 모델과 뷰 사이의 흐름을 제어합니다.
  2. Synchronous Processing:

    • 요청이 들어오면, 컨트롤러에서 해당 요청을 처리하고 응답을 반환할 때까지 클라이언트는 기다립니다. 이는 일반적으로 블로킹 방식으로 이루어집니다.

구성 요소

  1. Controller:

    • HTTP 요청을 처리하는 컴포넌트입니다. @Controller@RequestMapping 어노테이션을 사용하여 정의됩니다.
  2. Model:

    • 뷰에 표시할 데이터를 담고 있는 객체입니다. Model이나 ModelMap을 사용하여 데이터를 전달합니다.
  3. View:

    • 데이터를 시각적으로 표현하는 역할을 합니다. JSP, Thymeleaf, FreeMarker 등의 템플릿 엔진을 사용합니다.
  4. DispatcherServlet:

    • 중앙 제어 역할을 하는 서블릿으로, 모든 요청을 받아서 적절한 컨트롤러로 전달합니다.
  5. HandlerMapping:

    • 특정 요청 URL을 적절한 컨트롤러에 매핑하는 역할을 합니다.
  6. ViewResolver:

    • 컨트롤러가 반환한 논리적 뷰 이름을 실제 뷰로 변환하는 역할을 합니다.

구성도

다음은 Spring MVC 애플리케이션의 일반적인 구성도입니다:

┌────────────┐      ┌───────────────────┐        ┌───────────────┐      ┌──────────────┐
│   Client   │ ────▶│ DispatcherServlet │ ────▶  │   Controller  │ ────▶│   Service    │
└────────────┘      └───────────────────┘        └───────────────┘      └──────────────┘
                                                        │                       │
                                                        ▼                       ▼
                                                  ┌────────────┐        ┌──────────────┐
                                                  │    Model   │        │   Database   │
                                                  └────────────┘        └──────────────┘
                                                        │
                                                        ▼
                                                  ┌────────────┐
                                                  │    View    │
                                                  └────────────┘
  • Client: HTTP 요청을 보내는 클라이언트(브라우저, 모바일 앱 등).
  • DispatcherServlet: 모든 요청을 받아서 적절한 컨트롤러로 전달하는 중앙 제어 서블릿.
  • Controller: HTTP 요청을 처리하는 컴포넌트.
  • Service: 비즈니스 로직을 처리하는 계층.
  • Model: 뷰에 표시할 데이터를 담고 있는 객체.
  • View: 데이터를 시각적으로 표현하는 사용자 인터페이스.
  • Database: 데이터를 저장하고 조회하는 데이터베이스.

Webflux 의 기본 개념

Spring WebFlux는 Spring 5에서 도입된 반응형(Reactive) 웹 프레임워크입니다. 이를 통해 비동기 논블로킹(Non-blocking) 방식으로 효율적인 자원 관리를 할 수 있습니다. Spring WebFlux는 특히 높은 동시성을 필요로 하는 애플리케이션에서 성능을 극대화할 수 있습니다.

Node.js + Express 등의 비동기 서버 강세로, 비동기 논 블로킹의 효율성이 대두되며, 스프링에서도 Webflux 도입으로 트렌드를 따라가는 것으로 보입니다.(필자 생각)
다만 주의해야 할 점은, Webflux는 잘못 설계할 경우 동기+비동기 처리가 섞이면서 효율성이 떨어질 수 있습니다. 따라서, 가파른 학습곡선이 요구됩니다.

  1. Reactive Programming:

    • Reactive Programming은 데이터 스트림과 변경 사항의 전파를 다루는 프로그래밍 패러다임입니다. 이는 Observable, Flowable과 같은 리액티브 스트림을 통해 비동기 데이터 흐름을 처리합니다.
    • 주요 라이브러리로는 Project Reactor와 RxJava가 있습니다.
  2. Non-blocking I/O:

    • WebFlux는 Non-blocking I/O를 사용하여 요청을 처리합니다. 이는 요청과 응답이 블로킹 없이 비동기적으로 이루어져 높은 성능과 효율성을 제공합니다.
  3. Reactive Streams:

    • Reactive Streams는 비동기 스트림 처리를 위한 표준입니다. Spring WebFlux는 이를 통해 데이터의 비동기 스트림을 처리합니다.
  4. Publisher와 Subscriber:

    • Publisher는 데이터를 생성하고, Subscriber는 데이터를 소비합니다. Publisher는 여러 개의 Subscriber에게 데이터를 전송할 수 있습니다.

구성 요소

라우터와 핸들러를 구현하는 방법은 두가지가 있습니다.

  1. Router & Handler:

    • Router, Handler를 별도로 구현하는 방법. (Webflux 에서 권장하는 방식)
    • MVC 모델과 유사하게@RestController@RequestMapping 어노테이션을 사용.
  2. WebClient:

    • 비동기 및 블로킹 방식의 HTTP 요청을 처리하기 위한 클라이언트입니다. RestTemplate의 대체로 사용됩니다. (보통 API 서버로서 동작하기 때문에 View 가 없습니다.)
  3. DispatcherHandler:

    • Spring WebFlux에서 요청을 처리하고 응답을 생성하는 중앙 컴포넌트입니다. Spring MVC의 DispatcherServlet과 유사한 역할을 합니다.
  4. HandlerMapping:

    • 요청 URL과 이를 처리할 핸들러를 매핑하는 역할을 합니다.
  5. HandlerAdapter:

    • HandlerMapping에 의해 선택된 핸들러를 실행하는 역할을 합니다.

구성도

다음은 Spring WebFlux 애플리케이션의 일반적인 구성도입니다:

Spring MVC 와 다르게 구성도에서 DispatcherHandler 위치가 명확하지 않습니다. (비동기 처리 특성)

┌────────────┐      ┌───────────┐      ┌─────────────┐      ┌──────────────┐
│   Client   │ ────▶│  Router   │ ────▶│   Handler   │ ────▶│   Service    │
└────────────┘      └───────────┘      └─────────────┘      └──────────────┘
                                              │                     │
                                              ▼                     ▼
                                        ┌───────────┐        ┌────────────┐ 
                                        │ WebClient │        │  Database  │
                                        └───────────┘        └────────────┘
  • Client: HTTP 요청을 보내는 클라이언트(브라우저, 모바일 앱 등).
  • Router: 요청을 적절한 핸들러로 라우팅하는 컴포넌트.
  • Handler: 실제로 요청을 처리하는 함수나 메서드.
  • Service: 비즈니스 로직을 처리하는 계층.
  • WebClient: 다른 서비스나 API와 통신하기 위한 비동기 클라이언트.
  • Database: 데이터를 저장하고 조회하는 데이터베이스.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

// 컨트롤러를 사용하는 전통적인 방식
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public Mono<String> getUserById(@PathVariable String id) {
        // Simulate a service call
        return Mono.just("User " + id);
    }
}
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

// 핸들러와 라우터를 구현하는 새로운 방식
@Component
public class UserHandler {

    public Mono<ServerResponse> getUserById(ServerRequest request) {
        String userId = request.pathVariable("id");
        return ServerResponse.ok().body(Mono.just("User " + userId), String.class);
    }
}
   

// ----------------    별도의 파일     -------------------
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;

@Configuration
public class UserRouter {

    @Bean
    public RouterFunction<ServerResponse> route(UserHandler userHandler) {
        return RouterFunctions
                .nest(path("/users"),
                        RouterFunctions.route(GET("/{id}"), userHandler::getUserById));
    }
}

비교 및 결론

  1. @Controller 사용 방식:
  • 전통적인 Spring MVC 스타일과 유사. (학습곡선 낮음)
  • 단일 클래스에서 요청 매핑 및 핸들링 로직을 관리. (관리가 편함)
  • 직관적이고 익숙한 방식이지만, 대규모 애플리케이션에서는 코드가 복잡해질 수 있음.
  1. @Component 를 사용해서 핸들러, 라우터 분리 방식:
  • 함수형 스타일의 라우팅을 제공하여 더 유연한 요청 매핑 가능. (철학에 어울림)
  • 핸들러와 라우터를 분리하여 더 모듈화된 코드 구조 제공.
  • 함수형 프로그래밍에 익숙하다면 더 간결하고 유지보수하기 쉬운 코드를 작성할 수 있음.
  1. 결론:
  • 소규모 애플리케이션에서는 @Controller 방식을 사용하는 것이 더 직관적이고 간단할 수 있습니다.
  • 대규모 애플리케이션에서는 핸들러와 라우터 분리 방식을 사용하는 것이 더 모듈화된 코드 구조로 유지보수에 유리할 수 있습니다. (그리고 Webflux에서 권장)

참고자료

profile
Hello World!

0개의 댓글