모르지 않다는 것은 아는것과 다르다.

Spring

함수 기반 API 설계

채마스 2022. 3. 19. 21:03

함수형 엔드 포인트

  • Spring Web 의 엔드포인트를 함수형 스타일로 작성할 수 있다.
  • 애노테이션 방식과 함께 사용 가능하다.
  • 애노테이션 방식과 마찬가지로 DispatcherServlet 위에서 동작한다.
  • 불변성을 고려해서 설계된다.

 

기존의 애노테이션 방식

@RequestMapping("/api")
@RestController
public class APIPlaceController {

    @GetMapping("/places")
    public List<String> getPlaces() {
        return List.of("place1", "place2");
    }

    @PostMapping("/places")
    public Boolean createPlace() {
        return true;
    }

    @GetMapping("/places/{placeId}")
    public String getPlace(@PathVariable Integer placeId) {
        return "place " + placeId;
    }

    @PutMapping("/places/{placeId}")
    public Boolean modifyPlace(@PathVariable Integer placeId) {
        return true;
    }


    @DeleteMapping("/places/{placeId}")
    public Boolean removePlace(@PathVariable Integer placeId) {
        return true;
    }
}
  • 위와 같이 흔히 사용하는 애노테이션 방식으로 API 를 구현했다.
  • 이제 위의 API 방식을 함수 기반으로 바꿔보자

 

함수 기반 API 코드

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;

import java.util.List;

import static org.springframework.web.servlet.function.RequestPredicates.path;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration
public class APIPlaceRouter {

    @Bean
    public RouterFunction<ServerResponse> placeRouter() {
        return route().nest(path("api/places"), builder -> builder
                .GET("", req -> ServerResponse.ok().body(List.of("place1", "place2")))
                .POST("", request -> ServerResponse.ok().body(true))
                .GET("/{placeId}", request ->
                        ServerResponse.ok().body("place " + request.pathVariable("placeId")))
                .PUT("/{placeId}", request -> ServerResponse.ok().body(true))
                .DELETE("/{placeID}", request -> ServerResponse.ok().body(true))


        ).build();
    }
}
  • 위와 같이 함수형 기반으로 API 를 설계할 수 있다.
  • RouterFunction 은 ServerRequest 를 입력으로 받아 Optional 을 반환한다.
  • HandlerFuction 은 ServerRequest 를 입력으로 받아 ServerResponse 를 출력으로 한다.

 

HandlerFuction 별도 분리

  • 아래와 같이 HandlerFuction 을 별도로 분리할 수 있다.
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import java.net.URI;
import java.util.List;

@Component
public class APIPlaceHandler {

    public ServerResponse getPlaces(ServerRequest request) {
        return ServerResponse.ok().body(List.of("place1", "place2"));
    }

    public ServerResponse createPlaces(ServerRequest request) {
        return ServerResponse.created(URI.create("/api/places/1")).body(true);
    }

    public ServerResponse getPlace(ServerRequest request) {
        return ServerResponse.ok().body("place " + request.pathVariable("placeId"));
    }

    public ServerResponse modifyPlaces(ServerRequest request) {
        return ServerResponse.ok().body(true);
    }

    public ServerResponse removePlaces(ServerRequest request) {
        return ServerResponse.ok().body(true);
    }

}
  • 위에서 분리한 HandlerFuction 을 아래와 같이 RouterFunction 에 의존성을 주입시켜준다.
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;

import static org.springframework.web.servlet.function.RequestPredicates.path;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@Configuration
public class APIPlaceRouter {

    @Bean
    public RouterFunction<ServerResponse> placeRouter(APIPlaceHandler apiPlaceHandler) {
        return route().nest(path("api/places"), builder -> builder
                .GET("", apiPlaceHandler::getPlaces)
                .POST("", apiPlaceHandler::createPlaces)
                .GET("/{placeId}", apiPlaceHandler::getPlace)
                .PUT("/{placeId}", apiPlaceHandler::modifyPlaces)
                .DELETE("/{placeID}", apiPlaceHandler::removePlaces)
        ).build();
    }
}
  • HandlerFuction 를 분리하면 좀더 깔끔하게 구현할 수 있고 관리에도 용이하다.




 

REFERENCES

  • 김은호님의 SpringMVC

'Spring' 카테고리의 다른 글

MapStruct  (0) 2022.04.09
Handler Methods  (0) 2022.04.09
Spring Cache Abstraction  (0) 2022.03.19
@ControllerAdvice  (0) 2022.03.19
설정파일(Properties) 관리  (0) 2022.03.19