스프링

스프링에서 다른 서버 API 호출하기

yoon4360 2025. 4. 2. 22:00

현실에서 하나의 백엔드만으로 모든 기능을 구현하는 경우는 드물다.
결제 시스템, 사용자 인증 시스템, 추천 시스템 등 다른 서버의 API(REST 엔드포인트)를 호출해야 할 일이 많다.

스프링에서는 이런 REST 호출을 도와주는 세 가지 대표 방법이 있다.

  1. OpenFeign
  2. RestTemplate
  3. WebClient

이번 글에서는 각 방식이 어떤 상황에서, 어떻게 동작하고, 무엇이 다른지 정리해보려 한다.

 


 

다른 서버의 API 호출이란?

 

예를 들어 사용자가 결제 요청을 하면

사용자 -> 내 백엔드 앱 -> 결제 서버 -> 응답 

내 백엔드 앱은 사용자의 요청을 받아 다른 백엔드 API를 호출하고 결과를 받아 응답해야 한다. 이 역할을 수행하는 기술이 바로 OpenFeign, RestTemplate, WebClient이다.

 

 

REST 엔드포인트 구현

결제 서버에 해당하는 다른 서버는 8080 포트로 결제 서버 코드를 간단하게 구현했다.

@RestController
public class PaymentsController {
    private static Logger logger = Logger.getLogger(PaymentsController.class.getName()); // 엔드포인트가 호출될 때 올바른 데이터 가져오는지 확인

    @PostMapping("/payment")
    public ResponseEntity<Payment> createPayment(
            @RequestHeader String requestId,
            @RequestBody Payment payment
    ) {
        logger.info("Received request with ID" + requestId + " ;Payment Amount: " + payment.getAmount());
        payment.setId(UUID.randomUUID().toString());

        return ResponseEntity
                .status(HttpStatus.OK)
                .header("requestId", requestId)
                .body(payment);
    }
}

 

작동 방식은 클라이언트에서 요청을 보내면 8081 서버가 내부적으로 다른 8080 서버로 요청을 위임한다. 8081 서버는 프록시 역할을 하고 8080 서버가 실제 결제를 처리하고 응답을 반환하는 역할을 한다.

앞으로 구현할 세가지 방법 모두 8081 서버를 구현하는 것이다.

 

8081 서버의 applicaiton.yml 파일에 다음과 같이 url 설정을 해야한다.

server:
  port: 8081

name:
  service:
    url: http://localhost:8080

 

 


 

1. OpenFeign

OpenFeign은 인터페이스만 작성하면, 스프링이 실제 구현을 자동으로 만들어주는 도구이다.
복잡한 HTTP 호출 코드를 작성할 필요 없이, 메서드만 정의하면 된다.

 

OpenFeign 클라이언트 인터페이스를 정의한다. 

@FeignClient(name = "payments",url = "${name.service.url}") // REST 클라이언트 구성한다 (최소구성 정보인, 이름과 엔드포인트 기본 url 지정한다)
public interface PaymentsProxy {
    @PostMapping("/payment")
    Payment createPayment(
            @RequestHeader String requestId,
            @RequestBody Payment payment
    );
}

 

구성 클래스에서 OpenFeign 클라이언트 활성화 한다.

@Configuration
@EnableFeignClients(basePackages = "Spring.practice.payment_openfeign")
public class ProjectConfig {
}

 

이제 OpenFeign 클라이언트를 주입하여 사용한다.

@RestController
@RequiredArgsConstructor
public class PaymentsController {
    private final PaymentsProxy paymentsProxy;

    @PostMapping("/payment")
    public Payment createPayment(@RequestBody Payment payment) {
        String requestId = UUID.randomUUID().toString();
        try {
            return paymentsProxy.createPayment(requestId, payment);
        } catch (Exception e) {
            e.printStackTrace(); // 에러 로그 출력
            throw new RuntimeException("8080 서버 호출 실패: " + e.getMessage(), e);
        }
    }
}

 

장점
  • 인터페이스만 만들면 돼서 코드가 매우 간결하다.
  • Spring Boot + Spring Cloud와 궁합이 좋다.
  • @RequestParam, @RequestBody, @RequestHeader 등 기존 스프링 MVC 애너테이션을 그대로 사용 가능하다.
  • application.yml로 URL 등 환경값 주입이 가능하다.
주의할 점

 

  • @FeignClient 이름 충돌 피해야 한다.
  • 헤더 이름 정확히 명시해야 오류가 없다.
  • 예외 발생 시 FeignException으로 잡아야 디버깅이 가능하다.

 


 

2. RestTemplate 

RestTemplate은 예전부터 스프링에서 REST 호출에 사용되던 도구다.

이제는 레거시가 되었지만 아직 사용중인 프로젝트가 많다.
코드는 직접 작성해야 하지만 구조는 단순하다.

 

/payment 엔드포인트를 호출하는 앱의 PaymentProxy를 구현한다.

@Component
@RequiredArgsConstructor
public class PaymentsProxy {
    private final RestTemplate rest;

    @Value("${name.service.url}")
    private String paymentsServiceURl;

    public Payment createPayment(String requestId, Payment payment) {
        String uri = paymentsServiceURl + "/payment";

        // headers 객체 만들어 요청 헤더 정의
        HttpHeaders headers = new HttpHeaders();
        headers.add("requestId", UUID.randomUUID().toString());

        // httpEntity 객체 만들어 요청 데이터 정의
        HttpEntity<Payment> httpEntity =
                new HttpEntity<>(payment, headers);

        // Http 요청 전송하고 Http 응답 얻음
        ResponseEntity<Payment> response =
                rest.exchange(uri, HttpMethod.POST, httpEntity, Payment.class);

        return response.getBody();
    }
}

 

구현을 테스트하는 클래스를 정의한다.

@RestController
@RequiredArgsConstructor
public class PaymentsController {
    private final PaymentsProxy paymentsProxy;

    @PostMapping("/payment")
    public Payment createPayment(
            @RequestBody Payment payment
    ){
        String requestId = UUID.randomUUID().toString();
        return paymentsProxy.createPayment(requestId, payment);
    }
}

 

ProjectConfig 파일에 RestTemplate 빈 등록을 추가한다.

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

 

장점
  • 간단한 API 호출은 무난하게 가능하다.
  • 동기 방식으로 작동한다. (코드 읽기 쉬움)
단점
  • Spring 5부터 유지보수만 진행 중이고, 향후 제거 예정이다.
  • 매번 HTTP 요청 생성, 예외 처리 등을 직접 해야 해서 코드 많아진다.

 

3. WebClient 

WebClient는 리액티브 프로그래밍 기반의 REST 호출 방식이다.
동시 요청이 많은 시스템에 적합하고, 논블로킹 방식으로 작동한다.

 

일반 방식(블로킹)은

  • 앱이 요청을 받으면 새로운 스레드를 생성한다.
  • 이 스레드는 외부 시스템의 응답을 기다리는 동안 멈춰있다. (idle)
  • 응답이 올 때까지 아무 일도 못 하고 메모리만 잡아먹는다.

 

리액티브 방식(논블로킹)은

 

  • 앱이 요청을 받으면 작업을 작은 단위(Task)로 나눈다.
  • 한 스레드가 여러 요청의 Task를 돌면서 처리한다.
  • 외부 응답을 기다리는 동안은 다른 작업 먼저 처리한다.

 

 

우선 WebClient를 빈으로 등록한다.

@Configuration
public class ProjectConfig {
    @Bean
    public WebClient webClient() {
        return WebClient
                .builder()
                .build();
    }
}

 

 

WebClient로 프록시 클래스 구현한다. Mono라는 클래스를 사용해 생산자를 정의한다.

@Component
@RequiredArgsConstructor
public class PaymentsProxy {
    private final WebClient webClient;

    @Value("${name.service.url}")
    private String url;

    public Mono<Payment> createPayment(
            String requestId,
            Payment payment
    ) {
        return webClient.post() // 호출할 때 사용할 HTTP 메서드 지정
                .uri(url + "/payment")// 호출 URI 지정
                .header("requestId", requestId) // 요청 HTTP header 값 추가
                .body(Mono.just(payment), Payment.class) // HTTP 요청 본문 제공
                .retrieve() // HTTP 요청을 전송하고 HTTP 응답을 수신
                .bodyToMono(Payment.class); // HTTP 응답 본문 가져옴
    }
}

 

엔드포인트를 노출하고 프록시를 호출하는 컨트롤러 클래스이다.

@RestController
@RequiredArgsConstructor
public class PaymentsController {
    private final PaymentsProxy paymentsProxy;

    @PostMapping("/payment")
    public Payment createPayment(
            @RequestBody Payment payment
    ) {
        String requestId = UUID.randomUUID().toString();
        return paymentsProxy.createPayment(requestId,payment).block();
    }
}

 

장점
  • 고성능 처리가 가능하다. (특히 I/O가 많은 경우)
  • 요청/응답 간 비동기 흐름을 구성할 수 있다.
  • 리액티브 앱에 최적화되어 있다.
단점
  • 기존 MVC 방식과 완전히 다르다.
  • Mono, Flux 개념 등 학습 필요하다.
  • 리액티브 앱이 아닌 경우 사용을 권장하지 않는다.

 

정리

구분 OpenFeign RestTemplate WebClient
코드 간결성 ✅ 매우 간결 ❌ 직접 작성 필요 🔸 적당히
비동기/논블로킹 ❌ 불가능 ❌ 불가능 ✅ 가능
리액티브 지원 ❌ 없음 ❌ 없음 ✅ 전용
학습 난이도 ✅ 쉬움 ✅ 쉬움 ❌ 높음
스프링 권장 ✅ 적극 권장 ❌ 비권장 (레거시) ✅ 리액티브에서 권장
사용 추천 일반 앱 레거시 코드 유지 리액티브 기반 앱

 

 

결론

  • 일반 스프링 MVC 앱이라면 OpenFeign이 가장 쉽고 적합하다.
  • RestTemplate은 새로운 코드에선 사용하지 말자.
  • 리액티브 앱이라면 WebClient가 최고의 선택이다.
  • 리액티브가 아닌 앱에서는 WebClient는 오히려 복잡하고 불필요할 수 있다.