관리 메뉴

너와 나의 스토리

추가 배포 없이 API 케이스 통일하기: 카카오 사례 및 구현 가이드 본문

카테고리 없음

추가 배포 없이 API 케이스 통일하기: 카카오 사례 및 구현 가이드

노는게제일좋아! 2025. 4. 22. 20:16
반응형

 

카카오 기술 블로그 “추가배포 없이 API의 case 통일시키기”를 토대로 작성한 글입니다.

 

 

문제 정의

  1. 다양한 케이스 혼용
    • "Admin에서는 Camel case를 써요", "외부 연동처에서는 Snake case로 내려달라고 하네요" 등 의도치 않게 서비스별로 JSON 케이스가 혼용되어 코드베이스에 여러 Naming 전략이 섞이게 된다.
  2. DTO 중복
    • 여러 대상 서버의 케이스 규칙에 맞추려면 DTO를 여러 벌 정의해야 하는 상황이 발생한다.
(Camel Case) -> SERVER -> (Snake Case)
  • 동일한 값의 DTO지만 받을 때는 Camel case, 보낼 때는 Snake case가 필요한 상황이 있을 수 있다.
  • 위와 같은 문제는 MSA 환경에서 더욱 빈번하게 발생할 수 있고, 무중단 서비스에서는 쉽게 고치기 어렵다.

 

추가 배포 없이 API 케이스 통일하기 3단계

출처: https://tech.kakao.com/posts/665

 

  1. Case-Insensitive 파싱 모듈 도입
    • 핵심 목표: 기존 API가 어떤 케이스로 값을 보내든 간에 정상 동작하도록 만들기
    • 서버들이 camelCase 또는 snake_case JSON을 받아도 문제 없이 역직렬화할 수 있도록 Jackson의 기능을 활용.
    • ObjectMapper 설정에 accept-case-insensitive-properties: true 옵션을 설정하면, 케이스 무관하게 필드를 바인딩할 수 있다.
spring:
  jackson:
    mapper:
      accept-case-insensitive-properties: true

 

 

2. snake_case 응답/요청으로 일괄 전환

  • 핵심 목표: API를 외부에 snake_case로 일원화
  • 요청(Request)은 스프링에서 제공하는 RequestBodyAdvice를 활용하여 AOP 방식으로 camelCase -> snake_case로 변환된 JSON을 강제 파싱
  • 응답(Response)은 공통 ApiResponse<T> 래퍼를 사용해 내부 데이터를 가공한 뒤 snake_case로 직렬화.
  • 서버 응답 시 ObjectMapper에 PropertyNamingStarategies.SNAKE_CASE를 적용해 snake_case가 강제된다.
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    return mapper;
}

 

@ControllerAdvice
public class SnakeCaseRequestAdvice implements RequestBodyAdvice {

    private final ObjectMapper snakeCaseMapper;

    public SnakeCaseRequestAdvice(ObjectMapper defaultMapper) {
        this.snakeCaseMapper = defaultMapper.copy();
        this.snakeCaseMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

        // 모든 요청에 대해 Advice를 적용할지 여부
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                           Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // body를 직접 수정할 게 아니라면 그대로 반환
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
                                Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 여기서는 실제 바디 파싱이 끝난 이후라 바꾸는 작업은 거의 안 함
        return body;
    }

    @Override
    public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
                                  Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}

3. 모듈 정리 및 경량화

  • 핵심 목표: 호환 모듈을 제거하고 통일된 시스템으로 정리
  • 1단계에서 도입했던 case-insentive 파싱 모듈(accept-case-insensitive-properties)을 제거 -> 더 이상 필요 없어서
  • Jackson 관련 설정(RequestBodyAdvice)도 정리

 

이 접근 방식의 장점

  • 점진적 적용 가능
    • 기존 API 사용자가 인지하지 못한 채로 변화를 적용할 수 있어 위험이 낮음.
  • 코드 수정 최소화
    • DTO를 복제하거나 대규모 수정 없이도 명명 규칙을 바꿀 수 있음.
  • 플랫폼 일관성 확보
    • 외부 연동, 프론트엔드 API 호출 시 명확한 포맷 통일 가능.
  • 테스트/배포 비용 절감
    • 기존 API의 동작을 유지하면서 새로운 형식으로 전환 가능.
반응형
Comments