사전 버저닝
개념

버저닝

gabury1고친 사람 github-actions[bot]

버저닝은 바뀌어 가는 소프트웨어에 버전마다 번호를 붙여 서로 구별하게 하는 일입니다. 쓰는 쪽은 그 번호로 자기가 맞춘 버전을 골라 계속 부를 수 있습니다. 만드는 쪽이 새 버전을 내는 날에도 쓰던 코드가 갑자기 깨지지 않습니다. 소스 코드의 변경 이력을 쌓는 버전관리와는 다른 일입니다.

쉽고 빠른 이해

버저닝은 고친 버전마다 번호를 달아 「어느 버전을 쓰는지」 말할 수 있게 하는 일입니다. 주문 조회 주소가 /v1/orders 와 /v2/orders 로 나뉘어 있는 것이 그 모습입니다.

이게 없으면 서버가 응답 모양을 바꾸는 순간 옛 모양을 기대하던 앱이 전부 깨집니다. 사용자 폰에 깔린 앱은 서버와 같은 날 바뀌지 않기 때문입니다.

  1. 쓰는 쪽이 안 깨지는 변경은 지금 버전에 얹습니다
  2. 쓰는 쪽이 깨지는 변경은 새 버전 번호를 붙여 따로 냅니다
  3. 옛 버전은 미리 알린 기간 동안 함께 돌리다가 내립니다

대가는 손이 많이 간다는 것입니다. 버전이 둘이면 고칠 코드와 테스트와 문서도 둘이 됩니다. 두 쪽을 늘 함께 배포한다면 버전을 나눌 까닭이 줄어듭니다.

상세

대학 교재가 개정되면 교수는 「4판 120쪽을 펴세요」라고 말합니다. 판 번호가 없으면 학생마다 다른 책을 펴 놓고 같은 쪽수를 읽게 됩니다. 판 번호 하나가 모두를 같은 내용에 맞춰 줍니다.

이 절은 버저닝이 무엇을 약속하는지부터 봅니다. 그 약속이 왜 필요한지는 모바일 앱과 서버가 함께 도는 장면으로 보입니다. 이어서 어떤 변경이 버전을 올리게 하는지, 번호를 어떻게 매기는지, 옛 버전을 어떻게 내리는지를 차례로 봅니다.

소프트웨어에서는 교재의 판에 해당하는 것을 버전이라고 부릅니다. 버저닝(versioning)은 소프트웨어가 바깥에 내놓은 약속에 버전 번호를 붙이는 일입니다. 그 번호로 무엇이 바뀌었는지도 알립니다.

약속이란 쓰는 쪽이 기대어 코드를 짜는 모든 것입니다. 서버라면 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)의 주소와 요청·응답 모양입니다. 라이브러리라면 함수 이름과 인자입니다.

주문 조회 API 가 응답의 금액을 price 필드 하나로 주다가 amount 와 currency 둘로 나눈다고 해 봅시다. price 를 읽던 코드는 새 응답에서 값을 못 찾습니다. 이때 새 모양에 새 버전 번호를 붙여 따로 내놓는 것이 버저닝입니다.

GET /v1/orders/7  // price 하나
GET /v2/orders/7  // amount · currency

같은 주문 7번을 두 버전으로 부른 모습입니다. 옛 버전은 예전 모양을 지킵니다. 새 버전만 바뀐 모양으로 답합니다.

만드는 쪽과 쓰는 쪽의 배포 시점

버저닝이 필요한 까닭은 고치는 때가 서로 다르다는 데 있습니다. 서버는 오늘 배포하면 오늘 바뀝니다. 그 서버를 부르는 쪽은 그날 같이 바뀌지 않습니다.

모바일 앱이 대표적인 예입니다. 새 앱을 스토어에 올려도 사용자가 업데이트를 누르기 전까지 폰에는 옛 앱이 남습니다. 그래서 한 서버를 옛 앱과 새 앱이 동시에 부릅니다.

flowchart TD
    O["옛 앱 · price 를 읽는다"] --> V1["/v1/orders"]
    N["새 앱 · amount 를 읽는다"] --> V2["/v2/orders"]
    V1 --> S["주문 서버 하나"]
    V2 --> S

버전을 나누면 두 앱이 각자 기대하는 모양을 받습니다. 서버는 하나지만 응답 모양은 버전마다 따로 지킵니다.

다른 회사가 쓰는 공개 API, 다른 팀이 부르는 서비스, 남이 받아 쓰는 라이브러리도 사정이 같습니다. 만드는 쪽이 쓰는 쪽의 배포 날짜를 정할 수 없습니다.

깨는 변경과 안 깨는 변경

모든 변경에 버전을 올리지는 않습니다. 가르는 기준은 옛 코드가 손대지 않고도 계속 도는가입니다.

새 버전이 나와도 옛 버전에 맞춰 짠 코드가 계속 도는 성질을 하위 호환이라고 합니다. 앞의 주문 API 에서 price 를 남겨 둔 채 amount 를 곁에 더하면, 옛 앱은 계속 price 를 읽으며 돕니다. 이런 변경이 하위 호환을 지키는 변경입니다.

반대로 하위 호환을 깨뜨리는 변경은 하위 비호환 변경이라고 부릅니다. 실무에서는 브레이킹 체인지(breaking change)라고도 합니다. 이 문서에서는 줄여서 「깨는 변경」이라고 부릅니다.

아래 표는 흔한 변경이 어느 쪽에 드는지 보입니다.

변경 옛 코드 까닭
응답에 필드를 더한다 안 깨진다 옛 코드는 새 필드를 읽지 않는다
요청에 선택 값을 더한다 안 깨진다 안 보내면 예전처럼 동작한다
응답 필드를 뺀다 · 이름을 바꾼다 깨진다 읽던 값이 사라진다
필드의 타입을 바꾼다 깨진다 숫자를 기대한 곳에 글자가 온다
요청에 필수 값을 더한다 깨진다 옛 요청에는 그 값이 없다

표에서 볼 것은 방향입니다. 더하는 변경은 대개 안전합니다. 빼거나 바꾸는 변경은 대개 깨집니다. 버전을 올리지 않으려면 옛 필드를 두고 새 필드를 곁에 더하는 방법부터 찾습니다.

받은 응답을 프로그램의 객체로 바꾸는 일을 역직렬화라고 합니다. 역직렬화 코드는 모르는 필드를 만났을 때 그 필드를 무시할지, 오류를 낼지 고를 수 있습니다. 필드를 더하는 변경이 안전한 것은 쓰는 쪽이 무시하는 쪽을 골랐을 때뿐입니다.

오류를 내는 쪽을 고른 클라이언트라면 응답에 필드 하나만 더해도 깨집니다. 그러니 쓰는 쪽은 자기 역직렬화 코드가 어느 쪽으로 정해져 있는지 확인해 둡니다.

번호에 뜻을 싣는 시맨틱 버저닝

번호는 버전을 구별하는 데서 그치지 않고 무엇이 바뀌었는지도 알릴 수 있습니다. 라이브러리에서 흔한 방식이 시맨틱 버저닝입니다. 버전을 2.1.0 처럼 점으로 나눈 숫자 셋으로 적습니다. 칸마다 언제 올리는지도 정해 둡니다.

앞 칸부터 메이저, 마이너, 패치라고 부릅니다. 깨는 변경이면 메이저를 올립니다. 안 깨고 기능을 더하면 마이너를, 버그만 고치면 패치를 올립니다. 앞 칸을 올리면 뒤 칸은 0 으로 돌립니다.

1.4.2 → 1.4.3  // 버그만 고침
1.4.2 → 1.5.0  // 기능 추가 · 안 깸
1.4.2 → 2.0.0  // 깨는 변경

쓰는 쪽은 맨 앞 숫자만 보고도 올려도 되는지 가늠합니다. 맨 앞이 같으면 올려도 옛 코드가 돕니다. 맨 앞이 바뀌면 고칠 곳이 있다는 뜻입니다.

서버 API 는 보통 맨 앞 숫자만 드러냅니다. 주소에 v1, v2 처럼 숫자 하나만 적는 식입니다. 안 깨는 변경은 같은 버전에 얹으면 되므로 쓰는 쪽이 알아야 할 것은 깨는 변경뿐입니다.

버전 번호를 싣는 곳

서버 API 를 부를 때는 요청마다 어느 버전을 원하는지 서버에 알려야 합니다. HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜)로 부르는 API 에서는 세 방식이 흔합니다.

싣는 곳 모양 특징
주소 경로 /v2/orders/7 주소만 보고 버전을 안다. 같은 주문이 버전마다 다른 주소를 갖는다
쿼리 파라미터 /orders/7?version=2 주소의 뼈대는 같고 뒤에 붙인 값으로 고른다
요청 헤더 API-Version: 2 꼴 주소가 버전마다 안 바뀐다. 대신 주소만 보고는 어느 버전인지 모른다

헤더 방식에서 어느 헤더를 쓸지는 API 마다 정합니다. 표의 API-Version 은 꼴을 보이려고 붙인 이름입니다.

콘텐츠 협상은 클라이언트가 Accept 헤더로 받고 싶은 응답 형식을 밝히는 HTTP 의 방식입니다. 이 Accept 헤더의 값에 버전 번호를 섞어 싣는 방식도 있습니다.

Accept: application/vnd.example.v2+json

vnd.example 은 API 를 만든 쪽이 지어 붙인 형식 이름입니다. 그 안의 v2 가 버전 번호입니다.

어느 쪽을 골라도 약속은 같습니다. 쓰는 쪽이 버전을 직접 고릅니다. 고른 버전의 모양은 바뀌지 않습니다.

옛 버전을 내리는 순서

버전을 끝없이 함께 돌릴 수는 없습니다. 버전마다 코드와 테스트를 따로 지켜야 하기 때문입니다. 그래서 옛 버전은 정해진 순서를 밟아 내립니다.

첫 단계는 옛 버전을 지원 중단(deprecation) 상태로 돌리는 것입니다. 이 문서에서 지원 중단은 「아직 돌지만 곧 내린다」고 알려 둔 상태를 뜻합니다. 이 상태에서도 요청은 예전처럼 처리됩니다.

새 버전이 나오면 문서나 응답 헤더, 메일로 쓰는 쪽에 지원 중단을 알립니다. 끊을 날짜도 함께 밝힙니다.

지원 중단을 알린 날부터 끊는 날까지를 유예 기간이라고 합니다. 이 동안 쓰는 쪽은 새 버전으로 옮깁니다. 만드는 쪽은 옛 버전을 아직 누가 부르는지 호출 기록으로 지켜봅니다.

날짜가 되면 옛 버전을 실제로 내립니다. 이 문서에서는 이것을 종료라고 부릅니다. 종료 뒤 옛 주소로 온 요청에는 오류로 답합니다.

대가

버전이 늘수록 지킬 것이 늡니다. 버전 둘을 함께 돌리면 고칠 코드도, 돌릴 테스트도, 써 둘 문서도 둘씩입니다. 옛 버전에서 버그가 나면 두 버전을 다 고쳐야 할 수 있습니다.

쓰는 쪽도 비용을 냅니다. 새 버전으로 옮길 때마다 코드를 고치고 다시 시험해야 합니다. 버전을 자주 올리면 쓰는 쪽이 따라오지 못해 옛 버전에 머무는 곳이 늘어납니다.

깨는 변경은 모아서 한 번에 냅니다. 더하는 변경으로 바꿀 수 있으면 버전 올림을 피합니다.

버전을 나눌 필요가 없는 경우

버저닝은 쓰는 쪽의 배포를 만드는 쪽이 정할 수 없을 때 값을 합니다. 공개 API, 사용자 폰의 앱, 다른 팀이 쓰는 서비스, 배포한 라이브러리처럼 쓰는 쪽이 따로 배포되는 경우입니다.

두 쪽을 늘 함께 배포한다면 버전을 나눌 까닭이 줄어듭니다. 한 저장소 안에서 부르고 불리는 함수, 웹 화면과 그 화면 전용 서버를 한 번에 내보내는 구성이 그 예입니다. 양쪽을 같이 고쳐 같이 내보내면 둘이 어긋난 채 도는 때가 없습니다.

같은 이름의 다른 뜻

버저닝이라는 말은 다른 뜻으로도 쓰입니다. 소스 코드의 모든 변경을 기록해 두고 되돌릴 수 있게 하는 일은 버전관리라고 부릅니다. Git 같은 도구가 이 일을 맡습니다. 그쪽은 만드는 쪽 안에서 이력을 쌓는 일입니다. 이 문서의 버저닝은 바깥에 버전을 약속하는 일입니다.

객체 스토리지에서도 같은 낱말을 씁니다. 같은 이름으로 파일을 덮어써도 옛 내용을 지우지 않고 버전으로 남겨 두는 기능입니다. 실수로 덮어쓴 파일을 되살릴 때 씁니다.

메시지나 저장 데이터의 모양에 버전을 붙이는 일도 있습니다. 생각은 같지만 이쪽은 스키마 진화라는 이름으로 따로 다룹니다.

관련 항목

버저닝이 지키려는 호환성

하위 호환 · 상위 호환성 · 하위 비호환 변경 · 브레이킹 체인지 · 상호운용성

필드를 더해도 안 깨지게 받는 클라이언트 코드

역직렬화 · 직렬화

버전 번호를 매기는 규칙

시맨틱 버저닝 · 메이저 버전 · 마이너 릴리스 · 패치 릴리스 · 캘린더 버저닝 · 버전 번호

버전 번호를 싣는 요청의 부분

URL 경로 · 쿼리 파라미터 · 요청 헤더 · 콘텐츠 협상 · 미디어 타입

옛 버전을 내리는 절차

지원 중단 · 폐기 예고 · 유예 기간 · Sunset 헤더

버저닝이 적용되는 접점

API · API 설계 · 클라이언트 · 라이브러리 · SDK · REST · 모바일 앱

버전을 내보낼 때 함께 하는 작업

릴리스 노트 · 체인지로그 · 릴리스 엔지니어링 · 깃 태그

같은 낱말을 다른 뜻으로 쓰는 기술

버전관리 · Git · 객체 스토리지 · 스키마 진화

다른 이름: Versioning · 버전 매기기 · API 버저닝 · API 버전 관리