Functional Endpoints Overview

2024. 10. 13. 20:25Spring Framework/Web on Servlet Stack

Spring Web MVC: Functional Endpoints

Spring Web MVC에서는 기존의 @RequestMapping 기반 애너테이션 스타일을 대체할 수 있는 또 다른 방식으로 WebMvc.fn이라는 함수형 프로그래밍 모델을 제공합니다. 이 모델은 함수형 스타일을 사용하여 HTTP 요청을 라우팅하고 처리합니다. WebMvc.fn은 Spring WebFlux의 WebFlux.fn과 매우 유사한 구조를 가지고 있지만, 반응형(Reactive) 모델이 아닌 일반적인 서블릿 기반의 Web MVC와 함께 동작합니다.

핵심 개념

  1. HandlerFunction:
    • HandlerFunction@RequestMapping 메서드 본체에 해당하는 개념입니다. 즉, HTTP 요청을 처리하는 함수입니다. 이 함수는 ServerRequest를 아규먼트로 받아 ServerResponse를 리턴합니다.
    • ServerRequest는 HTTP 요청 데이터를 포함하고 있으며, 불변성을 지니고 있습니다. ServerResponse는 HTTP 응답을 처리하기 위한 객체로, 역시 불변성을 가지고 있습니다.
    public interface HandlerFunction<T extends ServerResponse> {
        T handle(ServerRequest request);
    }
  2. RouterFunction:
    • RouterFunction은 요청을 적절한 HandlerFunction으로 라우팅하는 역할을 합니다. 즉, RouterFunction은 요청이 들어오면 이를 처리할 HandlerFunction을 찾고, 해당 함수를 반환하는 구조입니다.
    • @RequestMapping 애너테이션과 유사한 역할을 하지만, 라우터 함수는 데이터뿐만 아니라 행동(비즈니스 로직)을 정의할 수 있다는 차이점이 있습니다.
    • 라우터 함수는 주어진 요청과 매칭되면 Optional<HandlerFunction>을 반환하고, 매칭되지 않으면 빈 Optional을 반환합니다.
    public interface RouterFunction<T extends ServerResponse> {
        Optional<HandlerFunction<T>> route(ServerRequest request);
    }
  3. 라우터 빌더:
    • RouterFunctions.route()는 라우터 빌더를 제공하여 라우터를 쉽게 생성할 수 있도록 돕습니다. 빌더 패턴을 사용하여 HTTP 메서드와 경로, 핸들러를 설정하고 빌드를 통해 최종 라우터를 생성합니다.
    • GET, POST, PUT, DELETE 등의 HTTP 메서드를 쉽게 매핑할 수 있으며, 경로와 accept 조건을 활용해 다양한 요청 조건에 맞는 핸들러를 설정할 수 있습니다.

기능적 엔드포인트의 주요 장점

  1. 함수형 스타일:
    • WebMvc.fn은 함수형 스타일을 지원하므로, 불변성을 유지하면서 코드를 더 간결하게 작성할 수 있습니다.
  2. JDK 8의 람다 및 메서드 참조 활용:
    • 함수형 프로그래밍 모델을 지원하기 때문에 람다식과 메서드 참조를 통해 직관적이고 간결한 코드를 작성할 수 있습니다.
  3. 가벼운 대안:
    • 기존의 애너테이션 기반 모델과 동일한 DispatcherServlet에서 실행되지만, 애너테이션을 사용하지 않고도 같은 작업을 할 수 있는 가벼운 대안입니다.
  4. 간결한 라우팅 및 핸들링:
    • URL 매핑, 요청 메서드에 대한 정의가 라우터 함수로 한 곳에서 간결하게 이루어질 수 있습니다.

구체적인 예시

다음 예시는 PersonHandler라는 클래스에서 사람 관련 요청을 처리하는 기능을 가진 라우터와 핸들러를 정의하는 코드입니다.

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.servlet.function.RouterFunction;

public class PersonHandler {
    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    // 사람 목록을 반환하는 핸들러
    public ServerResponse listPeople(ServerRequest request) {
        List<Person> people = repository.findAll();
        return ServerResponse.ok().contentType(APPLICATION_JSON).body(people);
    }

    // 새로운 사람을 생성하는 핸들러
    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        repository.save(person);
        return ServerResponse.ok().build();
    }

    // 특정 ID로 사람을 조회하는 핸들러
    public ServerResponse getPerson(ServerRequest request) {
        String id = request.pathVariable("id");
        Person person = repository.findById(id);
        if (person != null) {
            return ServerResponse.ok().contentType(APPLICATION_JSON).body(person);
        } else {
            return ServerResponse.notFound().build();
        }
    }
}

이 코드에서 PersonHandlerPersonRepository를 통해 데이터베이스에서 사람 정보를 가져오고, 새로운 사람을 생성하거나 조회할 수 있는 기능을 제공합니다.

라우터 정의

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();
  1. 라우터 빌더 사용:
    • route() 메서드를 통해 라우터 빌더를 시작합니다.
    • GET("/person/{id}"): 특정 ID의 사람 정보를 가져옵니다.
    • GET("/person"): 사람 목록을 가져옵니다.
    • POST("/person"): 새로운 사람을 추가하는 요청을 처리합니다.
    • 각 요청에 대해 해당 요청을 처리하는 HandlerFunction이 지정됩니다.

구체적인 핸들러 함수와 라우터 빌더 설명

  1. GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson):
    • HTTP GET 요청으로 /person/{id} 경로를 매핑하며, APPLICATION_JSON 헤더를 수락하는 요청에 대해 getPerson 핸들러 함수를 호출합니다.
    • handler::getPersonPersonHandler 클래스의 getPerson 메서드를 참조합니다.
  2. POST("/person", handler::createPerson):
    • HTTP POST 요청으로 /person 경로를 매핑하며, JSON 형식으로 새로운 사람 정보를 추가할 때 createPerson 핸들러가 호출됩니다.

RouterFunction을 Bean으로 등록하기

RouterFunction은 일반적으로 Spring Bean으로 등록되어 DispatcherServlet에 의해 자동으로 감지됩니다. 이를 위해 @Configuration 클래스를 사용하여 정의할 수 있습니다.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

@Configuration
public class RouterConfig {

    private final PersonHandler handler;

    public RouterConfig(PersonHandler handler) {
        this.handler = handler;
    }

    @Bean
    public RouterFunction<ServerResponse> personRoutes() {
        return route()
            .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
            .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
            .POST("/person", handler::createPerson)
            .build();
    }
}
  • @Configuration 클래스에서 RouterFunction<ServerResponse> 타입의 빈을 정의하여 라우터를 설정합니다. Spring은 이 빈을 자동으로 감지하고 설정된 라우팅 규칙에 따라 요청을 처리합니다.

Spring Web MVC의 WebMvc.fn은 기존 애너테이션 기반 프로그래밍 모델과 동일한 서블릿 기반 환경에서 함수형 스타일의 라우팅과 요청 처리를 제공합니다. 이 모델은 불변성을 유지하고, 람다식과 메서드 참조를 활용해 더 간결하고 직관적인 코드를 작성할 수 있게 합니다.

핵심 개념은 HandlerFunctionRouterFunction이며, 이 둘을 활용해 HTTP 요청을 처리하고, 필요한 데이터를 제공하거나 비즈니스 로직을 수행하는 방식을 설계할 수 있습니다.

'Spring Framework > Web on Servlet Stack' 카테고리의 다른 글

RounterFunction  (0) 2024.10.13
HandlerFunction  (0) 2024.10.13
Annotated Controllers  (0) 2024.10.09
Type Conversion  (0) 2024.10.09
Return Values  (0) 2024.10.09