Spring Webflux는 리액티브 웹 애플리케이션 구현을 위해 Spring 5부터 지원하는 리액티브 웹 프레임워크이다.
기술의 발달에 따른 하나의 요청을 하나의 쓰레드에서 처리하는 Blocking I/O 방식의 Spring MVC이 처리하지 못하는 상황이 잦아짐에 따라 적은 수의 쓰레드로 대량의 요청을 안정적으로 처리할 수 있는 비동기 Non-Blocking I/O 방식이 탄생했다

Spring Webflux는 클라이언트의 요청부터 응답까지 Reactor의 두가지 타입인 Mono, Flux의 Operator 체인으로 구성된 하나의 기다란 Sequence라고 생각하면 좀 더 쉽게 접근 할 수 있다

Spring Webflux가 쓰레드 차단 없이 더 많은 요청을 처리할 수 있는 이유는 요청 처리 방식으로 이벤트 루프 방식을 사용하기 때문이다

Component

1. HttpHandler

  • 다른 유형의 HTTP 서버 API로 request, response를 처리하기 위해 추상화된 1개의 메서드를 갖는다
public interface HttpHandler {
    Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);
}
  • HttpHandler 구현체
public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHandler {
    ...

    @Override
    public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
        ...
        ServerWebExchange exchange = createExchange(request, response);
    }
}

2. WebFilter

  • Spring MVC의 Servlet Filter처럼 핸들러가 요청을 처리하기 전에 전처리 작업을 해준다
  • 보안, 세션 타임아웃 처리 등
public interface WebFilter {
    Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
}

3. DispatcherHandler

  • WebHandler의 구현체로써 Spring MVC의 DispatcherServlet처럼 중앙에서 먼저 요청을 전달받은 후에 다른 컴포넌트에 요청 처리를 위임한다
  • ApplicationContext에서 HandlerMapping, HandlerAdapter, HandlerResultHandler 등의 요청 처리를 위임할 컴포넌트를 찾는다

4. HandlerMapping

  • Spring MVC에서와 마찬가지로 request와 handler object에 대한 맵핑을 정의하는 인터페이스
  • 구현체 : RequsetMappingHandlerMapping, RouterFunctionMapping
public interface HandlerMapping {
    Mono<Object> getHandler(ServerWebExchange exchange);
}
  • getHandler() : 파라미터로 입력받은 ServerWebExchange에 매칭되는 handler object를 리턴한다

5. HandlerAdapter

  • HandlerMapping을 통해 얻은 핸들러를 직접 호출하는 역할을 하며, 응답 결과로 Mono 리턴
  • 구현체 : RequestMappingHandlerAdapter, HandlerFunctionAdapter, SimpleHandlerAdapter, WebSocketHandlerAdapter
public interface HandlerAdapter {
    boolean supports(Object handler);

    Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler);
}
  • supports() : 파라미터로 전달받은 handler object를 지원하는지 체크
  • handle() : 파라미터로 전달받은 handler object를 통해 핸들러 메서드 호출

프로그래밍 모델

1. 어노테이션 기반 컨트롤러

  • Spring MVC에서 사용하는 @Controller 컨트롤러와 동일
  • 차이점은 리턴 타입으로 Mono를 사용

2. 함수형 엔드포인트(Functional Routing and Handling)

image

  • 함수형 엔드 포인트에서는 들어오는 요청을 라우팅하고, 라우팅된 요청을 처리하며 결과 값을 응답으로 리턴하는 등의 모든 작업을 하나의 체인에서 처리한다
  1. HandlerFunction을 사용한 request 처리
    • 들어오는 요청을 처리하기 위해 HandlerFunction 함수형 기반 핸들러 사용
    • Spring MVC에서의 요청 처리는 Servlet interface의 service()에서 파라미터로 전달받은 ServletRequest, ServletResponse에 의해 이루어진다
    • 반면 HandlerFunction은 ServerRequest 만 파라미터로 전달받고 응답은 Mono` 로 리턴
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
    Mono<T> handle(ServerRequest request);
}
  • ServerRequest
    • HandlerFunction에 의해 처리되는 HTTP request를 표현
    • Http headers, method, URI, query parameters, body 등에 접근 가능한 메서드 제공
  • ServerResponse
    • HandlerFunction or HandlerFilterFunction에서 리턴되는 HTTP response를 표현
  1. request 라우팅을 위한 RouterFunction
    • 들어오는 요청을 해당 HandlerFunction로 라우팅 해주는 역할
    • @RequestMapping과 같은 역할
    • route() : 파라미터로 전달받은 request에 매칭되는 HandlerFunction을 리턴
@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
    Optional<HandlerFunction<T>> route(ServerRequest request);
}
@Configuration("bookRouterV1")
public class BookRouter {
    @Bean
    public RouterFunction<ServerResponse> routeBook(BookHandler handler) {
        return route()
            .POST("/v1/books", handler::createBook)
            .PATCH("/v1/books/{book_id}", handler::updateBook)
            .GET("/v1/books", handler::getBooks)
            .GET("/v1/books/{book_id}", handler::getBook)
            .build();
    }
}
@Component
@RequiredArgsConstructor
public class BookHandler {
    private final BookService bookService;

    public Mono<ServerResponse> createBook(ServerRequest request) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(bookService.getAllBooks(), Book.class);
    }

    public Mono<ServerResponse> getBook(ServerRequest request) {
        String id = request.pathVariable("bookId");

        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(bookService.getById(id), Book.class);
    }
}

예외처리

  • Spring MVC에서 사용하던 @ExceptionHandler, @ControllerAdvice도 사용 가능하다
  • 아래는 Webflux에서만 사용 가능한 추가 처리방식이다
  1. onErrorResume()를 이용한 예외처리
    • 에러가 발생하면 에러를 downstream으로 전파하지 않고 대체 Publisher를 통해 대체 값을 emit하거나 에러 전환을 할 수 있다
    • onErrorResume(Throwable throwable, Publisher)
      • 첫번째 파라미터 : 처리할 exception
      • 두번째 파라미터 : 대체할 Publisher
return request.bodyToMono(BookDto.class)
    ....
	.onErrorResume(BusinessLogicException.class, error -> 
	    ServerResponse
		.badRequest()
		.bodyValue(
			new ErrorResponse(HttpStatus.BAD_REQUEST, error.getMessage())
			)
		)
  1. ErrorWebExceptionHandler를 이용한 글로벌 예외처리
@Order(-2)
@Configuration
public class GlobalWebExceptionHandler implements ErrorWebExceptionHandler {
    private final ObjectMapper objectMapper;
	
	TBD
}

Data Layer

  • Webflux는 SQL Mapper(MyBatis)나 ORM(JPA)와 함께 사용되기 어렵다. 동기적 블럭킹이기 때문이다.
  • MongoDB가 비동기적이고 논 블로킹 방식이기 때문에 WebFlux에서 함께 사용된다