함수형 엔드 포인트
- 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