T_era

RestTemplate 직접 생성 vs 빌더 생성 차이와 동작 원리 본문

Programing/Spring

RestTemplate 직접 생성 vs 빌더 생성 차이와 동작 원리

블스뜸 2025. 7. 8. 17:44

1. RestTemplate 직접 생성 방식

RestTemplate restTemplate = new RestTemplate();
  • 순수한 RestTemplate 인스턴스만 생성됨.
  • Spring Boot의 자동설정(메시지 컨버터, 커넥션 타임아웃, 커넥션 풀 등)이 적용되지 않음.
  • JSON 변환 등 메시지 컨버터를 직접 추가해야 함.
  • 커스텀 ClientHttpRequestFactory를 직접 지정해야 하며, 설정이 번거로움.
  • 장점:
    • PATCH 등 일부 HTTP 메서드 지원을 위해 커스텀 팩토리 지정이 가능.
  • 단점:
    • 설정이 불편하고, 실수로 인한 오류 가능성 높음.
    • Spring Boot의 편리한 자동설정 혜택을 못 받음.

PATCH 메서드 지원

  • Java 기본 HttpURLConnection은 PATCH를 지원하지 않음.
  • PATCH를 쓰려면 HttpComponentsClientHttpRequestFactory 등 별도 팩토리 지정 필요.
RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

2. RestTemplateBuilder를 통한 생성 방식

@Autowired
private RestTemplateBuilder restTemplateBuilder;

RestTemplate restTemplate = restTemplateBuilder.build();
  • Spring Boot가 제공하는 자동설정(메시지 컨버터, 타임아웃, 커넥션 풀 등)이 모두 적용됨.
  • JSON 변환, UTF-8 인코딩 등 대부분의 설정이 자동으로 적용되어 편리함.
  • 메서드 체이닝으로 커스텀 설정도 쉽게 가능.
  • 단점:
    • 기본적으로 내장 HttpURLConnection을 사용 → PATCH 메서드 미지원.
    • PATCH를 쓰려면 별도 팩토리 지정 필요(직접 생성 방식과 동일).

3. RestTemplate과 ClientHttpRequestFactory의 관계

  • RestTemplate은 직접 네트워크 통신을 하지 않고,
    내부적으로 ClientHttpRequestFactory라는 부품을 사용해 실제 HTTP 요청을 만듦.
  • 대표적인 팩토리 종류:
    • SimpleClientHttpRequestFactory (기본, HttpURLConnection 기반)
    • HttpComponentsClientHttpRequestFactory (Apache HttpClient 기반, PATCH 등 지원)
  • RestTemplate이 요청을 만들 때, 실제 네트워크 통신은 팩토리가 담당한다.

4. Spring Boot 자동설정 끄기

  • application.yml에서 아래처럼 자동설정을 끄면,
    RestTemplateBuilder 등에서 제공하는 자동설정이 적용되지 않음.
  • 이럴 경우, 직접 생성 방식과 동일하게 모든 설정을 직접 해줘야 함.
autoconfigure:
  exclude:
    - org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
    - org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration
    - org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration

5. 실무에서 주의할 점 & 추가로 알면 좋은 내용

  • 대부분의 경우 RestTemplateBuilder를 사용하는 것이 편리하고, Spring Boot의 장점을 살릴 수 있음.
  • PATCH 등 특수 HTTP 메서드가 필요하다면,
    RestTemplateBuilder로 생성하더라도 팩토리를 명시적으로 지정해주면 됨.
  • restTemplateBuilder .requestFactory(HttpComponentsClientHttpRequestFactory::new) .build();
  • RestTemplate는 Spring 5.0 이후로는 비동기/고성능이 필요한 경우 WebClient 사용을 권장함.
  • RestTemplate은 스레드 세이프하므로, 빈으로 등록해서 재사용하는 것이 좋음.

6. 예시 코드와 설명

private List<SearchProductResponse> productSearch(List<Long> productIds) {
    EventAddProductRequest eventAddProductRequest = new EventAddProductRequest(productIds);
    URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:8080")
            .path("/api/product/search-product")
            .encode()
            .build()
            .toUri();

    ResponseEntity<List<SearchProductResponse>> response = restTemplate.exchange(
            uri,
            HttpMethod.GET,
            new HttpEntity<>(eventAddProductRequest),
            new ParameterizedTypeReference<List<SearchProductResponse>>() {}
    );
    return response.getBody();
}

코드 설명

1. 요청 객체 생성

EventAddProductRequest eventAddProductRequest = new EventAddProductRequest(productIds);
  • API에 전달할 요청 객체를 생성.
  • 이 객체는 productIds(상품 ID 리스트)를 담고 있음.

2. URI 생성

URI uri = UriComponentsBuilder
        .fromUriString("http://localhost:8080")
        .path("/api/product/search-product")
        .encode()
        .build()
        .toUri();
  • UriComponentsBuilder를 사용해 요청할 API의 URI를 만듦.
  • .encode()를 호출하면, URI에 특수문자가 포함되어 있을 때 자동으로 인코딩.
  • .build().toUri()로 최종 URI 객체를 생성.

3. RestTemplate의 exchange 메서드 사용

ResponseEntity<List<SearchProductResponse>> response = restTemplate.exchange(
        uri,
        HttpMethod.GET,
        new HttpEntity<>(eventAddProductRequest),
        new ParameterizedTypeReference<List<SearchProductResponse>>() {}
);
  • exchange 메서드는 다양한 HTTP 메서드(GET, POST, PATCH 등)로 요청을 보낼 수 있음.
  • 첫 번째 인자: 요청할 URI
  • 두 번째 인자: HTTP 메서드 (여기서는 GET)
  • 세 번째 인자: 요청 본문과 헤더를 담는 HttpEntity 객체 (여기서는 요청 객체만 전달)
  • 네 번째 인자: 응답 타입을 지정하는데,
    제네릭 타입(예: List)을 받을 때는
    new ParameterizedTypeReference<List<SearchProductResponse>>() {}와 같이 사용해야
    타입 정보가 올바르게 전달됨.

4. 응답 결과 반환

return response.getBody();
  • 응답 객체에서 실제 데이터(List)만 꺼내서 반환.

추가 설명

단일 객체를 받을 때는 아래처럼 간단하게 사용할 수 있다.

restTemplate.getForObject(apiUrl, User.class);
  • 요청 객체가 필요한 경우(POST 등)에는 아래처럼 사용.
  • restTemplate.postForObject(apiUrl, eventAddProductRequest, User.class);
  • exchange는 다양한 HTTP 메서드와 복잡한 요청/응답 타입을 지원할 때 유용.

아래는 요청하신 보상 트랜잭션(Compensating Transaction) 관련 내용을 요약하여,
RestTemplate 정리글에 자연스럽게 추가할 수 있도록 작성한 예시입니다.


7. RestTemplate와 트랜잭션의 한계, 그리고 보상 트랜잭션

  • RestTemplate은 HTTP 통신을 통해 외부 시스템(마이크로서비스, 외부 API 등)과 데이터를 주고받는 용도로 사용된다.
  • 하지만 RestTemplate을 사용한 외부 호출은 Spring의 트랜잭션(@Transactional)과 직접적으로 연동되지 않는다.
    • 즉, 한 서비스 내에서 DB 트랜잭션이 롤백되어도, 이미 외부 시스템에 요청이 나간 경우 그 요청을 자동으로 취소할 수 없다.
    • 반대로, 외부 시스템에서 실패가 발생해도, 이미 커밋된 로컬 트랜잭션을 자동으로 롤백할 수 없다.
  • 이런 이유로, 분산 트랜잭션이 필요한 상황에서는 RestTemplate만으로는 트랜잭션의 원자성을 보장할 수 없다.

보상 트랜잭션(Compensating Transaction)이란?

  • 보상 트랜잭션은 분산 환경에서 트랜잭션의 원자성을 보장하기 어려울 때,
    실패 시 이미 수행된 작업을 "취소"하는 별도의 작업(보상 작업)을 추가로 수행하는 패턴.
  • 예를 들어, 서비스A에서 DB에 저장 후, 서비스B에 RestTemplate으로 요청을 보냈는데,
    서비스B에서 실패가 발생하면, 서비스A에서 이미 저장한 데이터를 삭제(취소)하는 로직을 추가로 실행하는 방식.
  • Saga 패턴 등에서 자주 사용되며, 마이크로서비스 아키텍처에서 데이터 일관성을 맞추기 위해 필수적으로 고려해야 하는 개념.

정리

  • RestTemplate을 사용하는 경우, 트랜잭션을 서비스 간에 직접 공유할 수 없으므로
    데이터 정합성을 위해 보상 트랜잭션(혹은 Saga 패턴 등)을 반드시 고려해야 한다.
  • 단일 서비스 내 트랜잭션과는 다르게,
    외부 시스템과의 연동에서는 "실패 시 어떻게 복구할 것인가"에 대한 설계가 필요.

결론

  • RestTemplateBuilder를 사용하면 Spring Boot의 자동설정과 편리함을 누릴 수 있다.
  • 직접 생성은 커스텀 설정이 필요할 때만 사용하고, 대부분의 경우 권장되지 않는다.
  • PATCH 등 특수 메서드가 필요하면 팩토리를 명시적으로 지정해야 한다.
  • 분산 트랜잭션이 필요한 상황에서는 문제가 발생할 수 있어서 복구 방법을 설계해야한다.
  • 실무에서는 WebClient로의 전환도 고려해보자.