| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- 스프링부트
- 배열
- vscode
- 단위테스트
- list
- Eclipse
- ArrayList
- Java
- input
- 자바스크립트
- 자바문법
- javascript
- math
- js
- 자바
- 인텔리제이
- 테스트자동화
- junit5
- SpringBoot
- CSS
- 문자열
- IntelliJ
- junit
- Visual Studio Code
- java테스트
- string
- Array
- HashMap
- 정규식
- html
- Today
- Total
어제 오늘 내일
[WebFlux] "컨트롤러가 없다고?" 함수형 엔드포인트(Router & Handler) 작성법 본문
지난 시간에는 @RestController를 이용한 익숙한 방식을 배웠습니다. 하지만 WebFlux의 창시자들은 조금 더 Java 8+ 람다(Lambda)스러운 방식을 제안합니다.
바로 함수형 엔드포인트(Functional Endpoints)입니다.
이 방식은 "요청을 어디로 보낼지(Routing)"와 "어떻게 처리할지(Handling)"를 완벽하게 분리합니다.
1. 두 가지 핵심: Router & Handler
마치 지하철 노선도와 같습니다.
- RouterFunction (지도): "이 URL로 오면 저기로 가세요." (길 안내)
- HandlerFunction (목적지): "오셨군요. 제가 처리해 드릴게요." (실제 업무)
MVC에서는 컨트롤러 안에 매핑(@GetMapping)과 로직이 섞여 있었지만, 여기서는 완전히 분리됩니다.
2. 핸들러(Handler) 만들기
먼저 실제 비즈니스 로직을 수행할 일꾼, 핸들러를 만듭니다.
MVC의 Controller 메서드와 비슷하지만, 리턴 타입이 무조건 Mono<ServerResponse>여야 합니다.
@Component
public class UserHandler {
private final UserRepository userRepository;
public UserHandler(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 1. 단건 조회
public Mono<ServerResponse> getUser(ServerRequest request) {
// @PathVariable 대신 request.pathVariable() 사용
Long id = Long.parseLong(request.pathVariable("id"));
return userRepository.findById(id)
.flatMap(user -> ServerResponse.ok().bodyValue(user)) // 찾으면 200 OK + Body
.switchIfEmpty(ServerResponse.notFound().build()); // 없으면 404 Not Found
}
// 2. 전체 조회
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.findAll(), User.class); // Flux<User>를 바디에 넣음
}
}
특징:
- 입력은
ServerRequest, 출력은ServerResponse로 고정됩니다. ok(),notFound(),badRequest()등 빌더 패턴으로 응답을 만듭니다.
3. 라우터(Router) 만들기
이제 핸들러를 URL과 연결해 줄 지도를 그립니다. 이건 설정 파일(@Configuration)에 빈(Bean)으로 등록합니다.
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> route(UserHandler userHandler) {
return RouterFunctions
// "/users/{id}"로 GET 요청이 오면 -> userHandler.getUser 실행
.route(GET("/users/{id}"), userHandler::getUser)
// "/users"로 GET 요청이 오면 -> userHandler.getAllUsers 실행
.andRoute(GET("/users"), userHandler::getAllUsers);
}
}
특징:
- 모든 API 엔드포인트가 한 눈에 보입니다. (MVC에서는 컨트롤러 파일을 다 뒤져야 했죠.)
andRoute()로 계속 체이닝해서 연결할 수 있습니다.
4. 어노테이션 방식 vs 함수형 방식 비교
"그럼 뭘 써야 하나요?" 정답은 없습니다. 팀의 취향 차이입니다.
| 특징 | 어노테이션 방식(@RestController) | 함수형 방식 (RouterFunction) |
| 익숙함 | MVC와 똑같아서 매우 익숙함 | 처음엔 낯설고 어려움 |
| 구조 | 매핑과 로직이 컨트롤러에 섞임 | 라우팅과 로직이 분리됨 |
| 가독성 | API가 많아지면 파악하기 힘듦 | 모든 URL이 한곳에 모여있어 파악 쉬움 |
| 성능 | 리플렉션 사용 (미세한 오버헤드) | 리플렉션 없음 (빠른 구동 속도) |
추천:
- 기존 팀원들이 MVC에 익숙하다면 -> 어노테이션 방식
- 새로운 마이크로서비스를 작게 만들거나, 명확한 라우팅이 필요하다면 -> 함수형 방식
5. 중첩 라우팅 (Nested Route)
함수형 방식의 가장 큰 장점은 "경로 그룹화"가 쉽다는 것입니다./users로 시작하는 모든 요청을 묶어볼까요?
@Bean
public RouterFunction<ServerResponse> nestedRoute(UserHandler handler) {
return RouterFunctions.route()
.path("/users", builder -> builder
.GET("/{id}", handler::getUser)
.GET("", handler::getAllUsers)
.POST("", handler::createUser)
)
.build();
}
코드 구조만 봐도 URL 구조가 보입니다. 정말 깔끔하죠?
마치며
오늘의 결론입니다.
- 함수형 엔드포인트는
@Controller없이 API를 만드는 모던한 방식이다. - Handler는 로직을 처리하고, Router는 길을 안내한다.
- RouterFunction을 쓰면 모든 API 목록을 한눈에 관리할 수 있다.
이제 여러분은 WebFlux의 두 가지 얼굴(어노테이션, 함수형)을 모두 다룰 수 있게 되었습니다.
다음 단계는 "남의 서버"를 호출하는 것입니다. RestTemplate은 이제 잊으세요.
다음 포스팅에서는 "RestTemplate은 이제 Deprecated!" 비동기 HTTP 요청의 정석, WebClient 사용법에 대해 알아보겠습니다. (이게 진짜 물건입니다!)
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
'IT > SpringBoot' 카테고리의 다른 글
| [R2DBC] "WebFlux에서 JPA 쓰면 망합니다!" R2DBC와 Reactive DB 연결 (0) | 2026.04.03 |
|---|---|
| [WebClient] "RestTemplate은 이제 Deprecated!" 비동기 HTTP 요청의 정석, WebClient 사용법 (0) | 2026.04.02 |
| [Spring WebFlux] 어노테이션 기반 컨트롤러: MVC 개발자라면 10분 만에 적응 가능! (0) | 2026.04.01 |
| [Reactor] "map과 flatMap, 도대체 뭐가 다른가요?" 마법의 연산자 정복하기 (0) | 2026.04.01 |
| [Reactor] "List랑 뭐가 달라요?" Mono와 Flux, 0개 1개 그리고 N개 (0) | 2026.03.31 |
