13.2 WebFlux 실전 활용 (WebClient 및 핸들러)
Spring WebFlux를 실제 프로젝트에서 어떻게 활용하는지, 특히 외부 API 호출을 위한 WebClient 사용법과 함수형 엔드포인트 구현 예제를 상세히 알아봅니다.
1. WebClient 상세 활용법
WebClient는 WebFlux 환경에서 외부 서비스와 통신할 때 사용하는 논블로킹 HTTP 클라이언트입니다.
1) WebClient 인스턴스 생성
두 가지 방식으로 인스턴스를 생성할 수 있습니다.
// 방법 1: 간단한 생성 (create)
WebClient client1 = WebClient.create("http://localhost:8080");
// 방법 2: 상세 설정을 포함한 생성 (builder) - 권장
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
2) GET / POST 요청 예제
@Service
@RequiredArgsConstructor
public class ReactiveApiService {
private final WebClient webClient;
// GET 요청: 단건 조회
public Mono<UserDto> getUser(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(UserDto.class);
}
// POST 요청: 데이터 전송
public Mono<UserDto> createUser(UserDto userDto) {
return webClient.post()
.uri("/users")
.bodyValue(userDto)
.retrieve()
.bodyToMono(UserDto.class);
}
}
2. 함수형 엔드포인트(Functional Endpoints) 상세 예제
함수형 방식은 요청을 처리하는 로직(Handler)과 주소 매핑(Router)을 명확히 분리합니다.
1) Handler 클래스 (비즈니스 로직)
@Component
public class UserHandler {
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
Flux<UserDto> users = Flux.just(
new UserDto(1L, "User1"),
new UserDto(2L, "User2")
);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(users, UserDto.class);
}
public Mono<ServerResponse> getUserById(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return ServerResponse.ok()
.bodyValue(new UserDto(id, "User" + id));
}
}
2) Router 클래스 (경로 매핑)
@Configuration
public class UserRouter {
@Bean
public RouterFunction<ServerResponse> userRoute(UserHandler handler) {
return RouterFunctions.route()
.GET("/api/v2/users", handler::getAllUsers)
.GET("/api/v2/users/{id}", handler::getUserById)
.build();
}
}
3. 리액티브 데이터 처리 예제 (R2DBC 예시)
데이터베이스 연동 시에도 논블로킹 라이브러리인 R2DBC를 사용해야 합니다.
@Repository
public interface UserRepository extends ReactiveSortingRepository<User, Long> {
// 반환 타입이 Flux나 Mono임에 주목
Flux<User> findByName(String name);
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public Mono<User> saveUser(User user) {
return userRepository.save(user);
}
public Flux<User> findUsers(String name) {
return userRepository.findByName(name);
}
}
4. 에러 핸들링 (Error Handling)
리액티브 스트림 내에서 발생하는 예외를 세련되게 처리할 수 있습니다.
public Mono<String> callApiWithRetry() {
return webClient.get()
.uri("/some-error-api")
.retrieve()
.bodyToMono(String.class)
.onErrorResume(e -> Mono.just("Fallback Data")) // 에러 발생 시 기본값 반환
.retry(3); // 에러 발생 시 3번 재시도
}