팀원이 올려주신 PR를 보고 코드를 이해하기 위한 정리글이다.
Refactor: 카카오 소셜 연결 해제에 OpenFeign 및 서킷 브레이커 적용 by Developer-Choi-Jae-Young · Pull Request #363 · Team-Unearned-Income/11th-1team-BEPR 타입(하나 이상의 PR 타입을 선택해주세요) Feat : 새로운 백엔드 기능 추가 Bug : 서버/로직 버그 발견 및 수정 Refactor : 백엔드 코드 구조 개선 Chore : 의존성/설정/인프라 유지보수 API : API 엔드포인트 및 스펙 변경 DB : DB 스키마/쿼리/마이그레이션 변경 Performance : 성능 개선 ...배경 이해하기
카카오에서는 회원 탈퇴시 연결 해제 API를 호출하도록 권장하고 있다.
https://developers.kakao.com/docs/ko/kakaologin/common#unlink
RestTemplate로 API 호출
그래서 기존에는 RestTemplate를 사용하여 카카오측 외부 API를 직접 호출했다.
@Override
public boolean requestUnlink(String providerId) {
String unlinkUrl = "https://kapi.kakao.com/v1/user/unlink";
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "KakaoAK " + kakaoAdminKey);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("target_id_type", "user_id");
body.add("target_id", providerId);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
try {
ResponseEntity<String> response = restTemplate.postForEntity(unlinkUrl, request, String.class);
return response.getStatusCode().is2xxSuccessful();
} catch (RestClientException e) {
log.error("카카오 연결 끊기(Unlink) 실패. Provider ID: {}, 에러: {}", providerId, e.getMessage());
return false;
}
} RestTemplate는 코드 단에서 URL를 직접 구성하여 HTTP를 호출한다.
- Header 생성
- Content-Type 설정
- Body 생성
- HttpEntity 생성
- POST 요청
- Response 처리
카카오 API의 에러가 우리 서비스로 전파
try-catch를 통해 카카오 unlink 요청이 실패시 서비스의 탈퇴 로직도 실패처리가 된다는 것이다.
즉, 카카오 서버에 의해 우리 서비스의 탈퇴 로직에 영향을 끼친다.
unlink 요청과 회원 탈퇴 로직을 분리하면 그만인걸까?
그렇게 된다면 일시적인 네트워크 오류로 인한 잠깐의 장애로 인해 특정 유저들은 unlink되지 않고 탈퇴되며 이를 추적하기에도 불편함이 있을 것이다.
무엇이 바뀌었을까?
우선 HTTP를 호출하는 방식이 OpenFeign 방식으로 바뀌었다.
OpenFeign이란?
Open Feign은 Netflix에 의해 처음 만들어진 Declarative(선언적인) HTTP Client 도구로써, 외부 API 호출을 쉽게할 수 있도록 도와준다. 여기서 “선언적인” 이란 어노테이션 사용을 의미하는데, Open Feign은 인터페이스에 어노테이션들만 붙여주면 구현이 된다. 이러한 방식은 Spring Data JPA와 유사하며, 상당히 편리하게 개발을 할 수 있도록 도와준다.
인터페이스의 추상 메서드에 아래와 같이 어노테이션만 붙여줘도 HTTP 요청이 가능하다.
@FeignClient(name = "kakao-unlink", url = "https://kapi.kakao.com")
public interface KakaoUnlinkFeignClient {
@PostMapping(
value = "/v1/user/unlink",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
String unlink(
@RequestHeader("Authorization") String authorization,
@RequestBody Map<String, ?> form
);심지어 해당 인터페이스의 구현체 없이 서비스 코드에 바로 선언해도 동작한다.
이는 Spring Cloud OpenFeign에서 Feign Proxy 객체 생성 후 Bean을 등록하기 때문이다.
그렇다면 단순히 간결한 코드 작성만을 위해 OpenFeign을 도입하는걸까?
외부 API 호출을 명시적인 Client 계층으로 분리하였기에 Circuit Breaker 적용 지점을 명확하게 할 수 있다는 점이다.
그리고 이러한 장점은 기존에는 카카오 외부 API 실패가 서비스 로직의 실패로 전파된다는 문제점을 해결해 줄 수 있다.
Circuit Breaker 패턴이란 마이크로서비스 아키텍처에서 서비스 장애가 전체 시스템에 미치는 영향을 방지하기 위해 사용되는 디자인 패턴이다.
Link to original
자바 진영에서는 Resilience4j와 같은 라이브러리를 통해 Circuit Breaker 패턴을 쉽게 구현할 수 있다.
// 위 unlink 메서드에 추가
@CircuitBreaker(name = "kakaoUnlink", fallbackMethod = "unlinkFallback")특히 인터페이스 default 메서드를 정의하여 어노테이션 속성을 통해 fallback 메서드를 등록할 수 있다.
default String unlinkFallback(String authorization, Map<String, ?> form, Exception e) {
var log = LoggerFactory.getLogger(KakaoUnlinkFeignClient.class);
if (e instanceof CallNotPermittedException) {
log.warn("생략");
} else if (e instanceof FeignException feignException) {
log.warn("실패", feignException.status());
} else {
log.warn("실패", e.getClass().getSimpleName());
}
return null;
}설정값을 통해 CircuitBreaker/Retry 동작 방식을 제어할 수 있으며 우리의 정책은 다음과 같다.
- 최근 최대 10개 호출을 관찰
- 최소 5개가 쌓이면 판단 시작
- 실패율 >= 50% OR 2초 이상 걸린 호출 비율 >= 50%
- OPEN
- 30초 동안 외부 호출 차단
- HALF_OPEN
- 최대 3회 시험 호출
- 정상 → CLOSED
- 장애 → OPEN
의문
의존성 추가 없이 Spring HTTP Service Clients로 구현 가능한데 Spring Cloud Open Feign을 도입해야 할까?

직접 테스트를 진행해봤다.
Intellij가 Bean을 인식 못하는 버그가 있었기에 IDE 자체를 버전업하는 이슈가 있었다.
base-url를 인식 못함


결국 해당 방식 또한 restclient 의존성을 추가해야 특정 http 클라이언트를 그룹화하여 관리할 수 있다.
implementation 'org.springframework.boot:spring-boot-starter-restclient'결론
기존 구조에서는 Kakao API의 지연이나 장애가 회원 탈퇴 기능까지 직접 전파되어, 외부 시스템의 상태에 따라 우리 서비스의 가용성에 영향을 받는 문제가 있었다.
그렇다고 일시적인 네트워크 오류 한두 번만으로 외부 호출을 모두 차단하는 것도 적절하지 않다. 따라서 OpenFeign을 통해 Kakao API 호출을 명시적인 외부 클라이언트 계층으로 분리하고, 해당 호출 경계에 Circuit Breaker를 적용하였다. 최근 호출의 실패율과 지연 비율이 일정 기준을 초과한 경우에만 일시적으로 외부 호출을 차단하여, 간헐적인 장애는 허용하면서 반복되는 외부 장애가 우리 서비스까지 전파되는 것을 방지한다.
