[Spring WebFlux] 17. 함수형 엔드포인트

y001·2025년 5월 5일

Reactive Programming

목록 보기
25/30
post-thumbnail

Spring WebFlux는 애너테이션 기반 프로그래밍 모델과 함께, 함수형 엔드포인트를 기반으로 하는 프로그래밍 모델도 지원한다.

1. HandlerFunction을 사용한 request 처리

WebFlux의 함수형 엔드포인트는 들어오는 요청을 처리하기 위해 HandlerFunction이라는 함수형 기반의 핸들러를 사용한다.

서블릿 기반의 요청 처리는 Servlet 인터페이스의 service(ServletRequest req, ServletResponse res) 메서드를 통해 이루어진다. 반면, WebFlux에서는 HandlerFunction이 사용된다.

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
  Mono<T> handle(ServerRequest request);
}
  • handle() 메서드는 하나의 ServerRequest를 입력으로 받고, Mono<ServerResponse>를 반환한다.

2. Request 라우팅을 위한 RouterFunction

RouterFunction은 들어오는 요청을 적절한 HandlerFunction에 라우팅해주는 역할을 한다. 이는 애너테이션 기반의 @RequestMapping과 유사한 기능을 수행한다.

@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
  Optional<HandlerFunction<T>> route(ServerRequest request);
}
  • route() 메서드는 요청을 특정 핸들러로 매핑할 수 있을 경우 그에 해당하는 HandlerFunction을 Optional로 감싸서 반환한다.
  • RouterFunction은 단순히 경로와 메서드를 매칭하는 것뿐만 아니라, 요청을 처리할 핸들러 함수까지 함께 정의할 수 있다.

예시: Book 리소스에 대한 요청을 라우팅하는 BookRouter

@Configuration("bookRouterV1")
public class BookRouter {
  @Bean
  public RouterFunction<ServerResponse> routeBook(BookHandler handler) {
    return RouterFunctions.route()
        .POST("/v1/books", handler::createBook)
        .PUT("/v1/books/{book-id}", handler::updateBook)
        .GET("/v1/books", handler::getBooks)
        .GET("/v1/books/{book-id}", handler::getBook)
        .build();
  }
}
  • RouterFunctions.route()RouterFunction.Builder를 반환하며, 각 HTTP 메서드에 대한 라우팅 설정을 체이닝 방식으로 구성할 수 있다.
  • 각 요청은 BookHandler 내 정의된 핸들러 메서드로 연결된다.

HandlerFunction vs RouterFunction

항목설명
HandlerFunction요청을 처리하는 실제 로직을 정의하는 함수형 인터페이스. 요청을 받아 응답을 반환한다.
RouterFunction들어오는 요청을 적절한 HandlerFunction에 라우팅해주는 역할을 한다. 경로와 메서드 기반 매핑 설정을 담당한다.

RouterFunction은 라우팅 설정을, HandlerFunction은 실제 요청 처리를 담당한다.


3. 함수형 엔드포인트에서 Request Body 유효성 검증

Spring의 Validator 인터페이스를 구현하거나 주입받아, 요청 본문(Request Body)에 대해 Bean Validation 기반의 유효성 검증을 수행할 수 있다.

예시: BookValidator 구현

@AllArgsConstructor
@Component("bookValidator")
public class BookValidator<T> {
  private final Validator validator;

  public void validate(T body) {
    Set<ConstraintViolation<T>> violations = validator.validate(body);
    if (!violations.isEmpty()) {
      onValidationErrors(violations);
    }
  }

  private void onValidationErrors(Set<ConstraintViolation<T>> violations) {
    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, violations.toString());
  }
}
  • javax.validation.Validator를 주입받아 validate() 메서드로 유효성 검사를 수행한다.
  • 유효하지 않은 경우 ResponseStatusException을 발생시켜 클라이언트에 400 응답을 보낸다.

예시: BookHandler에서 유효성 검증 적용

@AllArgsConstructor
public class BookHandler {
  private final BookValidator<BookDto.Post> validator;
  private final BookMapper mapper;

  public Mono<ServerResponse> createBook(ServerRequest request) {
    return request.bodyToMono(BookDto.Post.class)
        .doOnNext(validator::validate)
        .map(mapper::bookPostToBook)
        .flatMap(book ->
            ServerResponse
                .created(URI.create("/v1/books/" + book.getBookId()))
                .build()
        );
  }
}
  • bodyToMono()로 요청 바디를 BookDto.Post로 역직렬화한 후, .doOnNext()를 통해 유효성 검증을 수행한다.
  • 이후 도메인 객체로 변환(map)하여 실제 리소스를 생성하고 응답을 반환한다.

0개의 댓글