API 버저닝
방식 비교
| 방식 | 예 | 장점 | 단점 |
|---|
| URL 경로 | /v1/orders | 명확, 라우팅과 캐시 쉬움 | URI가 리소스 식별을 벗어남 |
| 쿼리 파라미터 | ?version=1 | 도입 쉬움 | 누락 시 기본값이 모호 |
| 헤더 | Accept: ...v1+json | URL 유지 | 디버깅과 캐시 불편 |
| 날짜 버전 | 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문제를 먼저 풀어볼 수도 있어요.