Foundry
API 설계
중급

API 버저닝

한번 나간 API는 마음대로 못 바꾼다

API 버저닝

방식 비교

방식예장점단점
URL 경로/v1/orders명확, 라우팅과 캐시 쉬움URI가 리소스 식별을 벗어남
쿼리 파라미터?version=1도입 쉬움누락 시 기본값이 모호
헤더Accept: ...v1+jsonURL 유지디버깅과 캐시 불편
날짜 버전2026-08-01세밀한 이력 관리운영 복잡도 상승

무엇이 파괴적 변경인가

더하는 변경은 옛 클라이언트를 깨뜨리지 않고 빼거나 뜻을 바꾸는 것은 깨뜨린다 옛 클라이언트가 깨지나 없어도 되는 필드를 더한다 안 깨진다 필드를 지운다 읽던 쪽이 깨진다 뜻이나 형식을 바꾼다 조용히 잘못 동작한다 셋째가 가장 나쁘다. 오류가 안 나서 늦게 발견된다 그래서 버전을 올리는 것은 아래 둘을 해야 할 때다 더하기만 하면 버전 하나로 오래 갈 수 있다
  • 파괴적: 필드 삭제, 이름 변경, 타입 변경, 필수 파라미터 추가, 에러 의미 변경
  • 비파괴적: 선택 필드 추가, 새 엔드포인트 추가, 응답에 값 추가

버전을 올리는 비용

버전을 올리면 두 벌을 함께 운영해야 합니다. 그것이 생각보다 비쌉니다.

늘어나는 것내용
코드 경로두 모양을 다 다뤄야 한다
시험두 벌을 다 확인해야 한다
저장소옛 모양을 만들 수 있게 데이터를 남겨야 한다
옛 버전을 끄는 일누가 쓰는지 알아야 끌 수 있다

마지막이 가장 오래 남습니다. 옛 버전을 끄지 못하면 버전이 계속 쌓입니다. 그래서 버전을 올릴 때 누가 어느 버전을 쓰는지 볼 수 있게 만들어 두는 것이 먼저입니다.

올리지 않고 바꾸는 방법

대부분의 변경은 버전 없이 할 수 있습니다.

필드를 더하되 없어도 되게 만든다
옛 필드를 남겨 두고 새 필드를 함께 채운다
옛 것을 안 쓰는 것을 확인한 뒤 지운다

더하기만 하면 버전 하나로 오래 갑니다. 지우는 것을 미루는 것이 곧 버전을 미루는 것입니다.

실무 포인트

  • 버전을 올리는 것보다 올리지 않아도 되게 설계하는 편이 싸다. 필드 추가는 하위호환이므로 v2를 만들 이유가 없다
  • 새 버전을 만들면 종료 계획을 함께 정한다. 지원 기간, 폐기 고지, 버전별 사용량 계측
  • 클라이언트가 배포를 제어할 수 없는 환경(모바일 앱)은 구버전이 오래 남는다. 강제 업그레이드 경로가 필요하다
  • 응답 파싱에서 미지의 필드를 허용하도록 클라이언트를 만들어 두면 서버 변경 폭이 넓어진다
면접에서 이렇게 나옵니다

Q.필드 하나를 제거해야 합니다. 새 버전을 만들지 않고 처리할 방법이 있나요?

버전을 올리지 않고 끝낼 수 있습니다. 지우는 것을 미루는 것이 방법입니다.

단계하는 일
1그 필드를 더는 채우지 않겠다고 문서에 적고 응답에는 계속 넣는다
2누가 그 필드를 실제로 읽는지 측정한다
3읽는 쪽에 개별로 알린다
4사용이 0이 된 뒤에 지운다

2단계가 핵심입니다. 읽는 쪽을 모르면 지울 수 없습니다. 응답 필드 단위 사용량이 안 잡히면, 그 필드를 참조하는 클라이언트 버전을 로그로라도 추려야 합니다.

기간을 정해 두는 것도 필요합니다. "언젠가 지운다" 는 영원히 안 지워집니다. 문서에 제거 예정일을 박고 그 날짜를 응답 헤더로도 알리면 상대가 일정을 잡습니다.

곧바로 지워야 하는 경우도 있습니다. 그 필드가 지금 문제를 만들고 있을 때입니다. 개인정보가 들어 있거나, 값이 틀린 채 나가고 있다면 미루는 것이 더 위험합니다. 그때는 값을 비우거나 마스킹해서 구조는 유지하고 내용만 먼저 없앱니다.

흔한 실수: 필드 하나 때문에 v2 를 만드는 것. 버전을 올리면 두 벌을 함께 운영해야 하고, 그 비용은 필드 하나를 남겨 두는 비용보다 훨씬 큽니다. 더하기와 남겨 두기는 버전을 안 올려도 되고, 지우기와 의미 바꾸기만 버전을 필요로 합니다.

Q.URL 버저닝과 헤더 버저닝의 트레이드오프를 설명해주세요

항목경로 (/v1/users)헤더 (Accept: ...v1+json)
눈에 보이나보인다. 브라우저로 바로 연다안 보인다. 도구가 필요하다
라우팅경로로 갈라 배포까지 나눌 수 있다앱 안에서 분기해야 한다
캐싱URL 이 다르니 그대로 갈린다Vary 를 정확히 걸어야 한다
자원 식별같은 자원에 주소가 둘이 된다주소가 하나로 유지된다
디버깅과 공유링크만 주면 재현된다요청 전체를 줘야 재현된다

네 번째가 헤더 방식의 이론적 강점이고, 나머지 전부가 경로 방식의 실무적 강점입니다. 그래서 공개 API 는 대개 경로를 씁니다. 쓰는 사람이 많을수록 "링크로 재현된다" 가 큽니다.

헤더 방식이 맞는 자리도 있습니다. 클라이언트가 우리 팀 것뿐이고, 버전이 자주 바뀌며, 자원 주소를 안정적으로 유지해야 할 때입니다. 내부 서비스 사이가 여기 해당합니다.

캐싱은 따로 짚어야 합니다. 헤더로 버전을 가르면 중간 캐시가 버전이 다른 응답을 같은 것으로 보고 섞어 줄 수 있습니다. Vary 를 정확히 걸어야 하고, 빠뜨리면 증상이 "가끔 옛 형식이 온다" 라서 원인을 찾기 어렵습니다.

흔한 실수: 헤더 방식이 더 REST 답다는 이유로 고르는 것. 형식적 올바름보다 누가 이 API 를 쓰고 무엇을 아쉬워하는가가 기준입니다.

Q.구버전 API를 종료하려면 어떤 절차를 밟아야 하나요?

종료는 공지가 아니라 측정으로 시작합니다.

순서하는 일없으면
1버전별 호출량과 호출자를 계속 집계한다끌 수 있는지 판단 자체가 안 된다
2종료일을 정해 문서와 응답 헤더로 알린다상대가 일정을 못 잡는다
3개별 연락. 상위 호출자부터공지는 대개 안 읽힌다
4짧은 차단을 예고하고 실행한다진짜 끌 때 처음 터진다
5완전 종료. 한동안 410 으로 답한다404 면 버그로 오인된다

1번이 없으면 나머지가 전부 추측이 됩니다. 버전별 호출자를 모르면 영원히 못 끕니다. 그래서 버전을 올리는 시점에 집계를 같이 넣어야 합니다.

4번을 특히 권합니다. 예고된 시간에 몇 분만 응답을 막아 보는 것입니다. 아직 남은 호출자가 그때 드러납니다. 진짜 종료일에 처음 알게 되는 것보다 훨씬 싸게 끝납니다.

마지막에 410 을 쓰는 이유는 404 와 뜻이 다르기 때문입니다. 410 은 "여기 있었는데 없앴다" 라서, 받는 쪽이 경로 오타가 아니라 종료임을 압니다.

흔한 실수: 공지 한 번으로 끝났다고 보는 것. 공지는 읽히지 않습니다. 호출량이 0이 된 것을 데이터로 확인한 뒤 끄는 것이 유일하게 안전한 순서입니다.

Q.모바일 앱 클라이언트 때문에 버전을 못 내리는 상황을 어떻게 관리하겠습니까?

모바일은 강제 업데이트 없이는 옛 버전이 영원히 남습니다. 사용자가 앱을 안 올리면 우리가 할 수 있는 일이 없습니다. 그래서 웹과 다르게 다뤄야 합니다.

수단내용
최소 지원 버전그 아래는 실행 시 업데이트를 요구한다
서버 주도 설정화면 구성과 기능 토글을 서버가 내려 준다
응답 관용성모르는 필드를 앱이 무시하게 만든다
버전별 사용량어느 앱 버전이 얼마나 남았는지 본다

세 번째가 처음부터 돼 있어야 나중이 편합니다. 앱이 모르는 필드를 만나면 깨지는 구조면, 서버는 필드 하나도 못 더합니다. 반대로 무시하도록 만들어 두면 더하기는 버전 없이 계속할 수 있습니다.

강제 업데이트는 강한 수단이라 함부로 쓰면 안 됩니다. 다만 보안 문제나 서버가 더는 그 계약을 지킬 수 없을 때는 써야 합니다. 그래서 최소 지원 버전 장치는 평소에 심어 두고 쓰지 않는 쪽이 맞습니다. 필요할 때 만들면 이미 늦습니다.

꼬리가 길어질 때는 옛 버전 전용 변환 계층을 두는 방법도 있습니다. 서버 내부는 새 모양으로 가고, 옛 앱에게만 옛 모양으로 바꿔 주는 얇은 층입니다. 유지 비용이 한 곳에 모여서 전 코드에 분기가 흩어지는 것보다 낫습니다.

흔한 실수: 앱 버전 분포를 안 보고 종료일을 정하는 것. 웹은 배포하면 끝이지만 모바일은 사용자가 올려 줘야 끝납니다. 분포를 먼저 보고 일정을 잡습니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

더 깊이 공부하기

읽었으면 문제로 확인해보세요

API 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.