티모지지

[Riot API] RestClient + HttpInterface 도입 한 이유 (1차)

changha. 2025. 6. 30. 12:20

🚨상황 발단

 

사이드 프로젝트로 진행 중인 Tim.gg 서비스가 있다. 

기능 중에 라이엇 외부 API를 활용하는 부분이 있다. 

이 기능의 목적은 최근 10개의 매치 정보를 가져와서 승/패 횟수를 파악하는 것이다. 

팀원의 기존코드로 작성했을 때 HttpClient로 작성되었었다. 

작동하는데에는 문제가 없었지만 무언가 읽기 힘들다는 느낌이 들었었다.

기존 RiotAPIService.class 코드 중 일부 메서드

보면 알겠지만 URL을 일일이 메서드 안에 정의해줘야 한다. 

매번 반복되는 HttpRequest, HttpResponse도 코드의 양을 늘이는 주범이다. 

 

코드의 또 하나 문제인 부분이 

 

여기서 List<String>으로 매치정보를 가져오고 있다.  

그 후 일일이 동기 방식으로 MatchSummaryDTO에 저장하고 있다.

10개의 정보를 하나씩 해결하니까 웹사이트에서 은근히 처리시간이 길다는 느낌을 받았다.

따라서 이 부분을 비동기식으로 바꾸어야겠다고 생각이 들었다. 

 

정리하자면 내가 해야될 것

1. 중복되는 외부 API호출 부분 분리

2. 매치 정보 동기 방식 -> 비동기 방식

 

그래서 어떤 기술을 사용할까?

 

무엇보다 협업 프로젝트이다 보니 가독성이 좋아야했다. 

찾아보니 HttpInterface라는 기술이 있었다. 

이 기술은 연결할 URL을 인터페이스 단에서 선언하는 방식이다. (하지만 HttpClient와는 호환되지 않는다.) 

흔히 사용하는 컨트롤러의 어노테이션 매핑과 비슷하다. (신세계... 정말 유지보수하기 쉬울 것 같다)

자세한 내용은 https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface

 

REST Clients :: Spring Framework

WebClient is a non-blocking, reactive client to perform HTTP requests. It was introduced in 5.0 and offers an alternative to the RestTemplate, with support for synchronous, asynchronous, and streaming scenarios. WebClient supports the following: Non-blocki

docs.spring.io

링크를 달아두겠다. 

그리고 RestClient와 WebClient중에서 고민을 했었다. 

WebClient는 리액티브 프로그래밍으로 동기/비동기 기능을 제공한다. 이 하나의 기술로 내게 필요한 조건(동기,비동기 모두 필요)을 만족한다는 게 매력적이었다. 

하지만 단점이 있었다. 

1. 외부 라이브러리 추가 (WebFlux) -> 사실 한 줄 딸각 추가하면 되니까 큰 문제는 안된다. 

2. 추가적인 학습이 필요함(처음 접하는데 Mono, Flux와 같은 객체를 보고 겁을 먹었다..) -> 이게 크다.. 나는 단지 매치 정보 10개 비동기로 하고 싶을 뿐인데 여기에 더 많은 시간투자를 하는건 낭비라고 생각했다. 

 

반면 RestClient는 나에게 딱 맞는 해결책이었다. 

왜냐면 스프링 프레임워크에 포함된 HTTP 클라이언트로 추가적인 라이브러리가 필요없다.

그리고 설정이 굉장히 심플했다. WebClient와 마찬가지로 플루언트 API기법(메서드 체이닝) 방식이라 가독성이 좋았다. 

RestClient 빈 등록 메서드

그리고 비동기가 필요한 부분은 스트림의 CompletableFuture을 사용하기로 했다. 

필요한 부분이 단순히 10개의 매치정보를 비동기로 하는 것이 목표이기 때문에 충분하다고 판단했다. 

전후 비교 해보자

구분 동기 처리  비동기 처리  개선율 
총 실행 시간 1,496ms 435ms 70% 단축
처리 방식 순차적 호출 병렬 호출 -
개별 API 응답 시간 129~168ms 동일 -
동시 처리 개수 1개 10개 10배 증가

 

List<CompletableFuture<MatchSummaryDTO>> futures = matchIds.stream()
        .map(matchId -> CompletableFuture.supplyAsync(() -> {
            try {
                DetailMatchInfoDTO detail = requestMatchInfo(matchId, puuid, runeData);
                return //내용 생략
            } catch (Exception e) {
                log.error("매치 요약 정보 생성 중 오류 발생: {}", matchId, e);
                return null;
            }
        }, matchInfoExecutor))
        .toList();

 

RestClientConfig에 등록된 스레드풀

사전에 매치 10개를 비동기로 하기위한 스레드풀은 빈으로 등록해놓았다.

이것을 활용해 CompletableFuture.supplyAsync()를 이용하여 비동기 처리를 진행완료하였다. 

사실 10개라 그런가 엄청 큰 시간차이는 느끼지 못했다. (자세히보면, 스핀이 비동기가 좀 더 빨리 사라진다.👍)

하지만..!

유지보수 측면에서는 기존 url하드코딩 + HttpClient방식의 코드보다 

RestClient + HttpInterface를 통해 정말 큰 성과를 얻었다고 볼 수 있다. 

앞으로 다른 라이엇 API를 이용해서 확장할 때 큰 도움이 될 것 같다.

 

 

 

비동기 방식

 

동기 방식