Spring Webflux
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)
- 함수형 엔드 포인트에서는 들어오는 요청을 라우팅하고, 라우팅된 요청을 처리하며 결과 값을 응답으로 리턴하는 등의 모든 작업을 하나의 체인에서 처리한다
- 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를 표현
- 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에서만 사용 가능한 추가 처리방식이다
-
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())
)
)
-
ErrorWebExceptionHandler
를 이용한 글로벌 예외처리
@Order(-2)
@Configuration
public class GlobalWebExceptionHandler implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
TBD
}
Data Layer
- Webflux는 SQL Mapper(MyBatis)나 ORM(JPA)와 함께 사용되기 어렵다. 동기적 블럭킹이기 때문이다.
- MongoDB가 비동기적이고 논 블로킹 방식이기 때문에 WebFlux에서 함께 사용된다