본문으로 건너뛰기
Advertisement

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번 재시도
}
Advertisement