왜 Spring webflux에서는 기존에 잘 쓰던 애너테이션을 대신 Router Function방식을 도입했을까?
애너테이션은 컴파일 타임까지 남아서 클래스정보에 남는 메타데이터임. 애너테이션에 작성한 문자열 상수는 바꿀수가 없다. Spring webflux 설계자들은 이런 방식을 선호하지 않았던듯 함.
RouterFunction: 라우팅 테이블이 평범한 코드를 실행해서 만들어지는 하나의 객체(빈)임. 그래서 일반적인 if, 반복문, 설정값, DB 조회 결과 등을 가지고 라우팅을 런타임에 조립할 수 있다.
그리고 스프링 WebFlux는 함수형 프로그래밍을 지향해서 어노테이션처럼 선언형 프로그래밍과 맞지 않는 것도 있음. 이게 큰것 같음.
결국 핵심은 함수들을 이용해 런타임에 핸들러 로직을 동적으로 구성가능하다는 점.
URL경로와 핸들러를 매핑한다. MVC에서 주로 사용하던 @GetMapping 등의 애너테이션의 역할수행.
// 기존 방식 (어노테이션)
@RestController
class UserController {
@GetMapping("/users/{id}")
fun getUser(@PathVariable id: Long): Mono<User> {
return userService.findById(id)
}
}
// Router Function 방식
@Configuration
class UserRouter {
@Bean
fun userRoutes(handler: UserHandler) = router {
"/users".nest {
GET("/{id}", handler::getUser)
POST("/", handler::createUser)
DELETE("/{id}", handler::deleteUser)
}
}
}
입출력은 항상 고정되어있다.
입력: ServerRequest (요청 모든 정보)
응답: Mono<ServerResponse> (응답)
응답이 항상
Mono라는 점에서 SpringWebFlux도 1요청 1응답이라는 속성만큼은 동기적임. 이에 반해 카프카 스트림즈 같은 경우에는 요청없이 지속적으로 데이터를 처리한다.WebFlux와 카프카 스트림즈 중간 정도에 있는게 SSE(Server-Sent Event), WebSocket임. 이들은 요청 1개에 Flux로 응답을 지속적으로 준다. 주식 호가창등 응답에 쓰임.
@Component
class UserHandler(private val userService: UserService) {
fun getUser(request: ServerRequest): Mono<ServerResponse> {
val id = request.pathVariable("id").toLong()
return userService.findById(id)
.flatMap { user ->
ServerResponse.ok().bodyValue(user)
}
.switchIfEmpty(
ServerResponse.notFound().build()
)
}
fun createUser(request: ServerRequest): Mono<ServerResponse> {
return request.bodyToMono<User>()
.flatMap { userService.save(it) }
.flatMap { saved ->
ServerResponse.created(URI.create("/users/${saved.id}"))
.bodyValue(saved)
}
}
}
참고로 Webflux에서 Router와 Handler는 서로 모름. 그래서 Handler는 독립적으로 테스트가 가능하다.