10/2

졸용·2025년 10월 2일

TIL

목록 보기
88/144

🔹 WebClient vs ResClient vs FeignClient vs RestTemplate

항목WebClientRestClientFeignClientRestTemplate
프로그래밍 모델리액티브(논블로킹)동기(블로킹) 중심, 비동기 일부선언형(인터페이스)동기(블로킹)
스트리밍(SSE/청크)최고(backpressure, Flux)제한적(직접 스트림 처리 필요)제한적(라이브러리·커스터마이징 의존)가능하나 불편
동시성/자원 효율매우 좋음보통보통(아래 HttpClient에 따라)보통
사용 난이도쉬움쉬움(선언형)쉬움(레거시)
관측성/필터링ExchangeFilter, Reactor hookInterceptorRequestInterceptorClientHttpRequestInterceptor
재시도/서킷브레이커Resilience4j, retryWhenResilience4jResilience4j 통합 용이Resilience4j
권장도(신규)높음높음조건부낮음

  • 스트리밍 또는 높은 동시성/효율이 핵심이면: WebClient 단일화가 가장 안정적.
  • 스트리밍이 전혀 없고, 호출이 단순하면: RestClient로 깔끔.
  • 선언형 인터페이스로 여러 외부/내부 서비스 통합이 목표면: OpenFeign + (스트리밍 엔드포인트만 WebClient 보조)

결국, 무엇을 중요하게 보느냐(스트리밍/성능/개발 편의/확장)에 따라 선택하면 좋을 것 같다고 생각되었다.

  • 스트리밍 토큰(SSE/청크 응답) / 대용량 / 동시성이 중요하다면 → WebClient
  • 단순 동기 HTTP 호출에 최신 문법/간결함이 중요하다면 → RestClient
  • 마이크로서비스처럼 엔드포인트가 많고 인터페이스로 선언형을 원한다면 → FeignClient (Spring Cloud Open Feign)
  • 레거시 코드 유지용이라면 → RestTemplate


이번 프로젝트에서는

  • 요청/응답이 완결형(논스트리밍)이고,
  • 단순 JSON POST/GET의 형태로 간결한 형태
  • 요청 보내고 결과 받아서 한 번에 DB 저장

이러한 요구사항을 충족하기 때문에, RestClient를 선택하는 것이 가장 적합하다고 생각되어, 구현 최소 예시를 찾아보았다.

🔹 RestClient (단건 동기 호출 + 전체 응답 저장)

@Configuration
public class AiRestClientConfig {
    @Bean
    RestClient aiRestClient(RestClient.Builder builder) {
        return builder
            .baseUrl("https://api.example.ai")
            .requestInterceptor((req, body, ex) -> {
                // Authorization 마스킹 로깅 등
            })
            .build();
    }
}
@Service
@RequiredArgsConstructor
public class AiSyncService {
    private final RestClient aiRestClient;
    private final AiLogRepository aiLogRepository;

    @Transactional
    public AiCallLog completeOnce(AiPrompt prompt) {
        long started = System.currentTimeMillis();

        var response = aiRestClient.post()
            .uri("/v1/chat/completions")
            .body(Map.of("model", prompt.model(), "messages", prompt.messages()))
            .retrieve()
            .toEntity(String.class);

        var log = AiCallLog.of(
            prompt, 
            response.getStatusCode().value(), 
            response.getBody(),
            System.currentTimeMillis() - started
        );
        return aiLogRepository.save(log);
    }
}

🔸 DB 스키마 팁(로그/감사용)

ai_call_log

  • id (PK), request_id(외부 idempotency), model, provider, prompt_hash
  • request_body(truncated), response_body(truncated), streamed(boolean)
  • http_status, error_message, latency_ms
  • input_tokens, output_tokens, cost(있다면)
  • created_at, completed_at, status(PENDING/PROCESSING/SUCCESS/FAILED)
profile
꾸준한 공부만이 답이다

0개의 댓글