프로젝트를 하다가 기존 Controller 구현되어있는 것들을 Router와 Handler로 변경을 하는 작업을 하고 있었는데 전부 구현을 하고 실행을 해보니까 Router가 작동 안되는 문제가 있었습니다.
처음에는 코드를 잘못 짠줄알고 엄청난 구글링 삽질을 하였지만 아무리 봐도 코드상에는 문제가 없는것같아서 의존성도 건들여보고 해봤지만 똑같은 상황이였습니다.
이런 상황에서 뭔가 킹리적 갓심으로 이거 지금 WebMvc가 적용되어있어서 Router가 작동이 안된다는 생각이 들었고 WebFlux에 대해 알아보다가 DispatcherServlet이 아닌 HttpHandler로 Request를 받는다는 내용을 보았고 삽질을 통해 스프링이 DispatcherServlet에 의해 Request를 받고 있다는 것을 확인하였습니다.
SERVLET으로 실행되었던 이유는 저희가 사용하던 라이브러리가 RestTemplate를 사용해 WebMvc를 사용하고 있어서 그렇다는것을 확인을 하였고 해당 라이브러리를 사용해 서비스를 구현하고 있었기 때문에
강제로 Reative 방식으로 실행을 시켜주었습니다.
@EnableReactiveMongoRepositories
@SpringBootApplication
class SpringApplication {
@Bean
fun restTemplate() = org.springframework.web.client.RestTemplate()
}
fun main(args: Array<String>) {
runApplication<SpringApplication>(*args) {
this.webApplicationType = WebApplicationType.REACTIVE
}
}
구글링 검색을 한 결과 WebApplicationType을 설정을 하여 해당방식으로 실행 해줄수 있다고 합니다.
WebApplicationType.None
: 응용 프로그램은 웹 응용 프로그램으로 실행되지 않아야하며 내장 웹 서버를 시작해서는 안됩니다. (SERVLET, REACTIVE둘다 없으면 NONE로 동작)WebApplicationType.SERVLET
: 서블릿 기반 웹 응용 프로그램으로 실행. 내장된 서블릿 웹 서버를 시작해야 한다. (Spring WebMvc가 들어있으면 기본적으로 SERVLET로 동작함)WebApplicationType.REACTIVE
: 반응형 웹 응용 프로그램으로 실행. 내장된 반응형 웹 서버를 시작해야 한다. (Spring WebFlux가 들어있으면 기본적으로 REACTIVE로 동작함)참고로 WebMvc, WebFlux 둘다 존재한다면 SERVLET으로 실행된다고 합니다.
그래서 저의 프로젝트도 SERVLET이 실행이 되었나 봅니다.
해당 문제점을 해결하고 나서 WebFlux 구조에 대해 제대로 모르는 것 같아서
자세히 공부를 해보았습니다.
WebFlux에 대해 자세히 알아보려면 먼저 WebMvc와 비교하면서 살펴보는 것이 좋을것 같습니다.
spring MVC는 Servlet spec에 기반하여 만들어져 있고 Servlet API과 Servlet container를 위해 구성되어 있습니다.
여러가지 특징 중 Tomcat과 같은 WAS에 의존적인 구조이고 Spring 3.1 스펙에서는 Non-Blocking I/O를 지원하지만 본질적으로 Blocking 이고 동기방식입니다. 따라서 전체 stack을 reactive하게 만들 순 없었습니다.
이러한 요구사항을 만족시키기 위하여 Spirng 5부터 대안적으로 도입한 모듈이 WebFlux입니다. 웹 요청을 reactive하게 다루는 데에 초점이 맞추어져 있습니다.
완벽한 Non-Blocking을 지원하고 Project Reactor를 통해서 Reactive Programming을 지원합니다.
또한 MVC와는 다르게 Servlet과 관계없이 만들어져 WAS가 필요없게 되었습니다.
위 그림에서 볼수있듯이 WebFlux는 MVC와 일부 호환이 됩니다.
MVC는 Servlet Container와 Servlet을 기반으로 웹 추상화 계층을 제공하는데 반해, WebFlux는 Servlet Container 뿐만 아니라,
Netty, Undertow와 같은 네트워크 애플리케이션 프레임워크도 지원합니다.
그래서 WebFlux 모듈이 포함하는 여러 기능을 이용해 2가지 프로그램 모델로 구성이 가능합니다.
Annotated Controller
Functional Endpoints
@Configuration
class Router(private val userHandler: UserHandler) {
@Bean
fun routerFunction(): RouterFunction<ServerResponse> {
return route()
.GET("/user/{userId}", accept(APPLICATION_JSON), userHandler::getUserById)
.GET("/user/comment/{id}", accept(APPLICATION_JSON), userHandler::getUserCommentById)
.build()
}
}
Router 설정하는 법은 되게 다양합니다. 저는 이런식으로 설정을 해주었고
매번 MEDIA TYPE을 설정하는게 귀찮다면 nest 형식으로 중첩을 방지 할 수도 있습니다.
@Component
class UserHandler(
val userService: UserService
) {
fun getUserById(request: ServerRequest): Mono<ServerResponse> {
val userId = request.pathVariable("userId");
// 조회 동작 기능...
// ...
val result = userService.getUser(userId) // 해당 Result는 Publisher 임
return ServerResponse.ok().contentType(APPLICATION_JSON).body(result, result.javaClass);
}
}
Handler는 Controller와 거의 유사한데 어노테이션이 사라지고
매개변수를 무조건 ServerReqeust
로 받아야하고
반환을 무조건 Mono<ServerResponse>
를 사용을 해야합니다. (route가 아닌 coRoute이면 그냥 ServerResponse
반환하면 되긴해요) (스프링 공식 문서 참고)
살짝 극혐이였던건 pathVariable를 저런식으로 가져와야 된다는 점? ㅋㅋㅋ
이런식으로 이제 개발을 진행하면 될 것 같습니다.
WebFlux 와 WebMvc를 동시에 지원을 하여 사용할 수 있다는 점을 처음알아서
아 이래서 이러한 문제가 발생하였구나를 이해를 하였고
진작에 WebFlux에 대해 자세히 알고있었으면 이런 문제를 삽질하지 않고 바로 해결을 하였을텐데..... 라고 생각을 하였습니다.
고작 Controller -> Router 변경 하는거에만 4~5시간동안 삽질을 하였네요........
울고싶다 ㅠㅠㅠㅠ
덕분에 WebFlux 구조에 대해 자세히 알게되었으니 좋은것 같습니다.
앞으로는 어떠한 새로운 기술을 사용할때는 해당 기술의 원리나 구조부터 자세히 이해하고 개발을 진행해야될 것 같습니다.