[Camel in Action] 10-2. Camel REST 클라이언트 – 외부 API 호출과 응답 처리

Camel을 REST 클라이언트로 사용하기

Camel은 REST 서버뿐 아니라 클라이언트로도 탁월합니다. 외부 API를 호출하고 응답을 처리하는 복잡한 로직을 라우트로 표현하면 유지보수가 훨씬 쉬워집니다.

기본 REST API 호출

// GET 요청
from("timer:apiCall?period=60000")
  .to("rest:get:https://api.example.com/users?bridgeEndpoint=true")
  .unmarshal().json(JsonNode.class)
  .process(exchange -> {
    JsonNode users = exchange.getIn().getBody(JsonNode.class);
    // 응답 처리
  });

// POST 요청
from("direct:createUser")
  .marshal().json()
  .setHeader(Exchange.HTTP_METHOD, constant("POST"))
  .setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
  .to("http4://api.example.com/users?bridgeEndpoint=true")
  .unmarshal().json(UserResponse.class);

OAuth 2.0 인증 처리

현대 API는 대부분 OAuth 토큰 인증이 필요합니다. Camel에서 토큰을 자동으로 갱신하는 패턴입니다.

// 토큰 캐싱 빈
@Component
public class TokenCache {
  private String token;
  private Instant expiry;

  public String getValidToken() {
    if (token == null || Instant.now().isAfter(expiry)) {
      refreshToken();
    }
    return token;
  }
}

// 라우트에서 토큰 주입
from("direct:secureApiCall")
  .process(exchange -> {
    String token = tokenCache.getValidToken();
    exchange.getIn().setHeader("Authorization", "Bearer " + token);
  })
  .to("http4://secure-api.example.com/data?bridgeEndpoint=true");

응답 코드별 처리 분기

API 응답 코드에 따라 다른 처리가 필요할 때는 Choice EIP를 활용합니다.

from("direct:apiCall")
  .to("http4://api.example.com/resource?bridgeEndpoint=true&throwExceptionOnFailure=false")
  .choice()
    .when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(200))
      .to("bean:successHandler")
    .when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(404))
      .log("리소스 없음")
      .stop()
    .when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(429))
      .log("요청 한도 초과, 재시도")
      .delay(60000)
      .to("direct:apiCall") // 재귀 호출로 재시도
    .otherwise()
      .to("direct:errorHandler")
  .end();

재시도와 폴백 패턴

외부 API는 일시적으로 실패할 수 있습니다. Camel의 Retry와 Circuit Breaker로 복원력 있는 클라이언트를 구현합니다.

from("direct:resilientApiCall")
  .circuitBreaker()
    .resilience4jConfiguration()
      .slidingWindowSize(10)
      .failureRateThreshold(50)
      .waitDurationInOpenState(30)
    .end()
    .to("http4://flaky-api.example.com/data")
  .onFallback()
    .setBody(simple("{"source":"cache","data":${bean:cacheService.getLatest}}"))
  .end();

페이지네이션 자동 처리

대용량 데이터를 페이지 단위로 가져오는 API를 처리할 때는 동적 라우팅으로 반복 호출을 자동화합니다.

from("direct:fetchAllPages")
  .setHeader("page", constant(1))
  .loopDoWhile(simple("${header.hasMore} == 'true' || ${header.page} == 1"))
    .toD("http4://api.example.com/items?page=${header.page}&bridgeEndpoint=true")
    .process(exchange -> {
      PagedResponse resp = exchange.getIn().getBody(PagedResponse.class);
      exchange.getIn().setHeader("hasMore", resp.hasNextPage());
      exchange.getIn().setHeader("page", resp.getPage() + 1);
    })
    .to("direct:processPage")
  .end();

Leave a Comment