ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수형 엔드포인트(Functional Endpoint)
    Spring Reactive Web Application/Spring WebFlux 2023. 8. 26. 10:00
    반응형

      클라이언트의 요청 처리를 위해 애너테이션 매핑 기법을 이용하는 애너테이션 기반 모델과는 달리, 함수형 엔드 포인트에서는 들어오는 요청을 라우팅하고, 라우팅된 요청을 처리하며 결과 값을 응답으로 리턴하는 등의 모든 작업을 하나의 함수 체인에서 처리합니다.

     

    HandlerFunction

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

    @FunctionalInterface
    public interface HandlerFunction<T extends ServerResponse> {
      Mono<T> handle(ServerRequest request);
    }
    • ServerRequest: HandlerFunction에 의해 처리되는 HTTP request를 표현합니다. ServerRequest 객체를 통해 Http headers, method, URI, query parameters에 접근할 수 있는 메서드를 제공하며, HTTP body 정보에 접근하기 위해 body(), bodyToMono(), bodyToFlux() 같은 별도의 메서드를 제공합니다.
    • ServerResponse: HandlerFunction 또는 HandlerFilterFunction에서 리턴되는 HTTP response를 표현합니다. ServerResponse는 BodyBuilder와 HeadersBuilder를 통해 HTTP response body와 header 정보를 추가할 수 있습니다.

     

      HandlerFunction은 RouterFunction을 통해 요청이 라우팅된 이후에 동작합니다.

     

    RouterFunction

      RouterFunction은 들어오는 요청을 해당 HandlerFunction으로 라우팅해주는 역할을 합니다. @RequestMapping 애너테이션과 동일한 기능을 합니다. 단, RounterFunction은 요청을 위한 데이터뿐만 아니라 요청 처리를 위한 동작(HandlerFunction)까지 RouterFunction의 파라미터로 제공한다는 차이점이 있습니다.

    @FunctionalInterface
    public interface RouterFunction<T extends ServerResponse> {
             Optional<HandlerFunction<T>> route(ServerRequest request);
             
             ...
             ...
    }

      route() 메서드에서 파라미터로 전달받은 request에 매치되는 HandlerFunction을 리턴합니다.

     

      이전 포스팅의 BookController를 함수형 엔드포인트로 변경하면 다음과 같습니다.

     

    request를 라우팅하는 라우터(BookRouter.java)

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.reactive.function.server.RouterFunction;
    
    import static org.springframework.web.reactive.function.server.RouterFunctions.route;
    
    @Configuration("bookRouterV1")
    public class BookRouter {
        @Bean
        public RouterFunction<?> routeBookV1(BookHandler handler) {
            return route()
                    .POST("/v1/books", handler::createBook)
                    .GET("/v1/books", handler::getBooks)
                    .GET("/v1/books/{book-id}", handler::getBook)
                    .build();
    //        return route(POST("/v1/books"), handler::createBook)
    //                .andRoute(GET("/v1/books/{book-id}"), handler::getBook)
    //                .andRoute(GET("/v1/books"), handler::getBooks);
        }
    }
    • route() 메서드는 RouterFunctionBuilder 객체를 리턴합니다.
    • RouterFunctionBuilder 객체로 각각의 HTTP Method에 매치되는 request를 처리하기 위한 라우트(route)를 추가합니다.
    • POST(), GET() 메서드의 첫 번째 파라미터는 매치되어야 할 URI 패턴이고, 두 번째 파라미터는 매치되는 request를 처리할 HandlerFunction 객체입니다. DI 받은 BookHandler 객체(handler)로 각각의 핸들러 메서드를 호출합니다.
    • build() 메서드를 호출하여 최종적으로 RouterFunction을 생성합니다.

     

    라우팅된 request 처리 핸들러(BookHandler.java)

    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;
    
    import java.net.URI;
    import java.time.LocalDateTime;
    import java.util.List;
    
    @Component("bookHandlerV1")
    public class BookHandler {
        private final BookMapper mapper;
    
        public BookHandler(BookMapper mapper) {
            this.mapper = mapper;
        }
    
        /**
          * 데이터베이스에 저장하는 로직은 생략된 상태입니다.
          * created() 메서드를 통해 생성된 리소스의 URI를 Header에 포함합니다.
          */
        public Mono<ServerResponse> createBook(ServerRequest request) {
            return request.bodyToMono(BookDto.Post.class)
                    .map(post -> mapper.bookPostToBook(post))
                    .flatMap(book ->
                            ServerResponse
                                    .created(URI.create("/v1/books/" + book.getBookId()))
                                    .build());
        }
    
        public Mono<ServerResponse> getBook(ServerRequest request) {
            long bookId = Long.valueOf(request.pathVariable("book-id"));
            Book book =
                    new Book(bookId,
                            "Java 고급",
                            "Advanced Java",
                            "Kevin",
                            "111-11-1111-111-1",
                            "Java 중급 프로그래밍 마스터",
                            "2022-03-22",
                            LocalDateTime.now(),
                            LocalDateTime.now());
            return ServerResponse
                                .ok()
                                .bodyValue(mapper.bookToResponse(book))
                                .switchIfEmpty(ServerResponse.notFound().build());
        }
    
        public Mono<ServerResponse> getBooks(ServerRequest request) {
            List<Book> books = List.of(
                    new Book(1L,
                            "Java 고급",
                            "Advanced Java",
                            "Kevin",
                            "111-11-1111-111-1",
                            "Java 중급 프로그래밍 마스터",
                            "2022-03-22",
                            LocalDateTime.now(),
                            LocalDateTime.now()),
                    new Book(2L,
                            "Kotlin 고급",
                            "Advanced Kotlin",
                            "Kevin",
                            "222-22-2222-222-2",
                            "Kotlin 중급 프로그래밍 마스터",
                            "2022-05-22",
                            LocalDateTime.now(),
                            LocalDateTime.now())
            );
            return ServerResponse
                    .ok()
                    .bodyValue(mapper.booksToResponse(books));
        }
    }
    • bodyToMono(): ServerRequest에서 제공하는 메서드로, request body의 데이터를 Mono<BookDto.Post> 객체로 변환해 줍니다.
    • bodyValue(): 파라미터로 전달한 객체를 response body로 변환합니다. body(BodyInserter)의 단축 표현입니다.
    • switchIfEmpty(): Operator 내부에서 원하는 정보가 없을 시 404 status 정보를 추가합니다.

     

     

     

    [참고 정보]

    반응형

    'Spring Reactive Web Application > Spring WebFlux' 카테고리의 다른 글

    예외 처리  (0) 2023.09.16
    R2dbcEntityTemplate  (0) 2023.09.10
    Spring Data R2DBC  (0) 2023.09.09
    애너테이션 기반 컨트롤러(Annotated Controller)  (0) 2023.08.26
    Spring WebFlux 개요  (0) 2023.08.22

    댓글

Designed by Tistory.