메이저 버전
고친 사람 github-actions[bot]
메이저 버전은 이번 판이 기존 사용자의 코드를 깨뜨릴 수 있다고 알리는 번호입니다. 버전 번호의 맨 앞 칸이 이 일을 맡습니다. 이 숫자가 오르면 쓰는 쪽은 자기 코드를 고쳐야 할 수도 있다는 것을 미리 압니다.
쉽고 빠른 이해
무슨 일을 하는 물건인가 — 「이번 판은 옛 코드와 안 맞을 수 있다」고 번호로 알립니다. 라이브러리가 2.7.3 다음에 3.0.0 을 내면 맨 앞 숫자가 오른 것입니다. 이 판은 옛 코드를 깨는 변경을 담고 있습니다.
왜 이렇게 하나 — 받는 쪽은 새 판을 받을 때마다 무엇이 바뀌었는지 다 읽을 수 없습니다. 맨 앞 숫자가 그대로면 판만 올려도 됩니다. 오르면 시간을 내서 옮겨 가야 합니다. 번호 하나로 이 둘을 가려 줍니다.
어떻게 도나
- 만드는 쪽이 옛 코드를 깨는 변경을 모읍니다
- 맨 앞 숫자를 하나 올리고 뒤 칸은 0 으로 되돌려 내놓습니다
- 받는 쪽 도구는 맨 앞 숫자가 다른 판을 저절로 받지 않습니다
- 받는 쪽 사람이 바뀐 점을 읽고 코드를 고친 뒤 옮겨 갑니다
대가 — 옮겨 가는 일이 받는 쪽 모두에게 생깁니다. 옮겨 가지 못한 사람이 옛 판에 남아서, 만드는 쪽은 옛 판과 새 판을 한동안 함께 돌봅니다.
상세
단골이 많은 식당을 떠올려 봅시다. 메뉴를 몇 가지 더하는 것은 단골에게 아무 일도 아닙니다. 늘 가던 길로 와서 늘 먹던 것을 시키면 됩니다. 그런데 식당이 다른 동네로 이사를 가면 단골은 새 주소를 알고 오는 길도 바꿔야 합니다.
버저닝은 소프트웨어를 낼 때마다 판에 번호를 붙여 어느 판인지 가리는 일입니다. 번호는 대개 2.7.3 처럼 점으로 나뉜 여러 칸으로 적습니다. 메이저 버전은 그 맨 앞 칸입니다. 위 비유의 이사에 해당합니다.
메이저 버전을 가르는 기준은 하위 호환입니다. 하위 호환은 새 판으로 바꿔도 옛 판을 쓰던 코드가 고치지 않은 채 도는 성질입니다. 이 성질을 깨는 변경이 하나라도 든 판은 메이저 버전을 올립니다.
하위 호환을 깨는 변경을 하위 비호환 변경이라고 부릅니다. 실무에서는 브레이킹 체인지(breaking change)라고도 많이 부릅니다. 메이저 버전은 「이 판에 브레이킹 체인지가 들었다」는 표시입니다.
버전 번호의 세 칸
메이저 버전이 다른 칸과 어떻게 갈리는지는 널리 쓰이는 번호 규칙 하나로 보면 잘 드러납니다. 칸마다 올리는 때가 다릅니다.
시맨틱 버저닝(Semantic Versioning)은 버전 번호를 메이저 · 마이너 · 패치의 세 칸으로 적는 규칙입니다. 칸마다 언제 올리는지를 정해 두어서, 번호만 보고 이번 판에 무엇이 바뀌었는지 짐작하게 합니다. 메이저 아래 두 칸의 판은 각각 마이너 릴리스, 패치 릴리스라고 부릅니다.
| 칸 | 예 | 올리는 때 | 받는 쪽이 할 일 |
|---|---|---|---|
| 메이저 | 2.7.3 → 3.0.0 |
옛 코드를 깨는 변경이 들었을 때 | 바뀐 점을 읽고 코드를 고칠 수 있다 |
| 마이너 | 2.7.3 → 2.8.0 |
옛 코드를 안 깨고 기능을 더했을 때 | 새 기능을 쓸 때만 손댄다 |
| 패치 | 2.7.3 → 2.7.4 |
옛 코드를 안 깨고 버그만 고쳤을 때 | 판만 올린다 |
표에서 메이저 칸만 받는 쪽에 일을 줍니다. 앞 칸을 올리면 뒤 칸은 0 으로 돌아갑니다. 그래서 2.7.3 다음 메이저 판은 3.7.3 이 아니라 3.0.0 입니다.
옛 코드를 깨는 변경
어떤 변경이 메이저 버전을 올리게 할까요? 시험은 하나입니다. 옛 판을 쓰던 코드가 고치지 않은 채 새 판에서 도느냐입니다.
API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)는 한 프로그램이 다른 프로그램을 부르는 약속입니다. 기준이 되는 것은 그중 바깥에 쓰라고 내놓은 공개 API입니다. 안에서만 쓰는 코드는 얼마든지 바꿔도 메이저 버전과 상관없습니다.
흔한 변경 여섯을 메이저를 올리는지로 가르면 이렇습니다.
| 변경 | 메이저를 올리나 | 까닭 |
|---|---|---|
| 함수를 없애거나 이름을 바꾼다 | ✓ | 부르던 코드가 그 이름을 못 찾는다 |
| 함수의 인자를 바꾸거나 필수 인자를 더한다 | ✓ | 옛 방식으로 부르면 실패한다 |
| 응답에 있던 필드 이름을 바꾼다 | ✓ | 옛 이름을 읽던 코드가 빈 값을 얻는다 |
| 약속한 동작을 바꿔 같은 입력에 다른 결과를 낸다 | ✓ | 코드는 돌아도 결과가 틀린다 |
| 함수나 응답 필드를 새로 더한다 | ✗ | 옛 코드는 새것을 모르고 지나간다 |
| 안에서만 쓰는 코드를 고친다 | ✗ | 바깥에서 안 보인다 |
넷째 줄은 버그 고침과 헷갈리기 쉽습니다. 버그 고침도 같은 입력에 다른 결과를 내기 때문입니다. 둘을 가르는 것은 약속입니다. 약속한 동작 자체를 바꾸면 메이저입니다. 약속과 어긋나게 돌던 동작을 약속대로 바로잡으면 패치입니다.
응답 필드 이름을 바꾸는 경우를 코드로 보겠습니다. 옛 판은 사용자 이름을 name 으로 주었고 새 판은 fullName 으로 줍니다.
user.name // "유미" · 옛 판
user.name // undefined · 새 판
user.fullName // "유미" · 새 판
옛 코드는 새 판에서도 오류 없이 돕니다. 다만 이름 칸에 undefined 가 찍힙니다. 이런 변경은 눈에 덜 띄어서 더 위험합니다. 메이저 버전을 올려 받는 쪽이 바뀐 점을 읽고 오게 합니다.
메이저 0 이 뜻하는 것
0.4.2 처럼 맨 앞이 0 인 판은 다른 약속을 따릅니다.
시맨틱 버저닝에서 메이저 0 은 아직 공개 API 를 굳히지 않은 초기 개발 단계를 뜻합니다. 이 단계에서는 어느 판에서든 옛 코드를 깨는 변경이 들 수 있습니다. 쓰는 쪽은 0.4 에서 0.5 로만 올려도 코드가 깨질 각오를 합니다.
1.0.0 은 만드는 쪽이 공개 API 를 처음으로 약속하는 판입니다. 이 판부터 옛 코드를 깨려면 메이저 버전을 올려야 합니다. 그래서 1.0.0 을 내는 일은 「이제부터 함부로 안 바꾸겠다」는 선언이기도 합니다.
웹 API 의 메이저 버전
웹 API 는 서버가 네트워크 너머로 내놓은 API 입니다. 여기서 받는 쪽은 이 API 를 부르는 클라이언트 앱입니다.
라이브러리는 받는 쪽이 원할 때 새 판으로 올립니다. 웹 API 는 서버가 하나라서 서버를 새 판으로 바꾸면 모든 클라이언트 앱이 그 순간 새 판을 받습니다. 아직 고치지 못한 앱은 바로 깨집니다.
그래서 웹 API 는 메이저 버전을 주소에 넣어 옛 판과 새 판을 함께 열어 둡니다. URL(Uniform Resource Locator)은 요청을 보낼 주소입니다. 경로 앞머리에 v1, v2 처럼 메이저 번호를 붙입니다.
GET /v1/users/42 // 옛 앱이 부른다
GET /v2/users/42 // 새 앱이 부른다
두 요청은 같은 사용자를 가리킵니다. 옛 앱은 v1 주소로 옛 모양의 응답을 받습니다. 고친 앱은 v2 주소로 새 모양을 받습니다. 서버 한 대가 두 판을 함께 내주는 셈입니다.
주소에는 메이저 번호만 넣습니다. 마이너와 패치 변경은 옛 앱을 안 깨므로 주소를 바꿀 까닭이 없습니다. 주소 대신 요청 헤더에 판을 적게 하는 서비스도 있습니다. 헤더는 요청 본문과 따로 붙는 이름표 같은 정보입니다.
옛 메이저를 닫는 순서
새 메이저를 냈다고 옛 메이저를 바로 닫을 수는 없습니다. 옛 판을 바로 닫으면 옮겨 오지 못한 쪽이 깨집니다. 영원히 열어 두면 만드는 쪽의 일이 끝나지 않습니다.
옛 판은 대개 세 상태를 거칩니다. 새 판이 나오기 전에는 그냥 운영 중입니다. 새 판이 나오면 「곧 닫는다」고 알리는 폐기 예고(deprecation) 상태가 됩니다. 알린 기간이 지나면 닫힙니다.
stateDiagram-v2
state "운영 중 · 옛 판만 열림" as 운영중
state "폐기 예고 · 옛 판과 새 판이 함께 열림" as 폐기예고
state "닫힘 · 새 판만 열림" as 닫힘
[*] --> 운영중
운영중 --> 폐기예고: 새 메이저를 낸다
폐기예고 --> 닫힘: 알린 기간이 끝난다
닫힘 --> [*]
폐기 예고 기간이 받는 쪽에게 주는 옮겨 갈 시간입니다. 이 기간에 만드는 쪽은 마이그레이션 가이드를 냅니다. 마이그레이션 가이드는 옛 판에서 새 판으로 옮길 때 무엇을 어떻게 고치는지 적은 문서입니다.
라이브러리는 주소를 나눠 열어 두는 대신, 옛 메이저에 고침을 얼마나 더 내줄지 기간을 약속합니다. 새 메이저가 나온 뒤에도 고침을 오래 내주기로 약속한 판이 장기 지원(Long-Term Support, LTS) 판입니다.
약속한 기간이 끝나면 그 판에는 더는 고침이 안 나옵니다. 이것이 지원 종료입니다. 지원이 끝난 옛 메이저에 남아 있으면 보안 구멍이 생겨도 메워지지 않습니다.
받는 쪽 도구가 메이저를 다루는 법
라이브러리를 받아 쓰는 쪽에서는 도구가 메이저 버전을 걸러 줍니다. 번호의 약속을 도구가 믿고 움직입니다.
의존성은 프로젝트가 가져다 쓰는 남의 코드입니다. 의존성을 내려받고 판을 맞추는 도구가 패키지 매니저입니다.
많은 패키지 매니저는 의존성의 판을 하나로 박지 않고 범위로 적게 해 줍니다. 이 범위가 버전 범위입니다. 적는 꼴은 도구마다 다르지만 「1 로 시작하는 판」은 대개 이런 뜻으로 적습니다.
>=1.0.0 <2.0.0 // 1 로 시작하는 판 전부
이 범위를 적어 두면 새 판이 나왔을 때 아래처럼 갈립니다.
1.4.3 // 범위 안 · 저절로 들어온다
1.9.0 // 범위 안 · 저절로 들어온다
2.0.0 // 범위 밖 · 사람이 올린다
마이너와 패치는 저절로 들어오고 메이저만 막힙니다. 옛 코드를 깰 수 있는 판은 사람이 변경 로그를 읽고 고른 뒤에 받으라는 뜻입니다. 변경 로그는 판마다 무엇이 바뀌었는지 적은 기록입니다.
같은 라이브러리의 두 메이저가 부딪칠 때
한 프로젝트 안에서 같은 라이브러리의 서로 다른 메이저가 함께 필요해질 때도 있습니다. 의존성이 의존성을 부르기 때문에 생기는 문제입니다.
예를 들어 내 앱이 라이브러리 A 와 B 를 씁니다. A 는 라이브러리 X 의 1 판을 씁니다. B 는 X 의 2 판을 씁니다. 1 판과 2 판은 메이저가 달라서 서로 바꿔 끼울 수 없습니다.
flowchart TD
앱["내 앱"] --> A["라이브러리 A"]
앱 --> B["라이브러리 B"]
A -->|"1 판이 필요"| X["라이브러리 X"]
B -->|"2 판이 필요"| X
한 프로그램에 X 를 하나만 실을 수 있는 환경이면 둘 중 하나는 맞지 않는 판을 받게 됩니다. 이렇게 의존성끼리 판이 엇갈려 풀리지 않는 상태를 의존성 지옥이라고 부릅니다. 모양이 마름모라서 다이아몬드 의존성 문제라고도 합니다.
푸는 길은 대개 둘입니다. A 나 B 가 X 의 같은 메이저로 맞출 때까지 기다리는 것이 하나입니다. 새 메이저를 아예 다른 이름의 패키지로 다뤄 두 판을 함께 싣는 것이 다른 하나입니다. 어느 쪽이 가능한지는 언어와 패키지 매니저가 정합니다.
메이저를 아껴 올리는 까닭
만드는 쪽은 메이저 버전을 되도록 드물게 올리려 합니다. 메이저 하나를 올릴 때마다 드는 비용이 받는 쪽 수만큼 곱해지기 때문입니다.
메이저를 올리면 받는 쪽 모두가 옮겨 갈 일을 떠안습니다. 옮기지 못한 쪽은 옛 판에 남습니다. 만드는 쪽은 그 옛 판에도 보안 고침을 내줘야 합니다. 메이저가 자주 오르면 돌볼 판이 계속 늘어납니다.
그래서 만드는 쪽은 브레이킹 체인지를 미루고 모았다가 한 번에 냅니다. 이름을 바꾸고 싶으면 새 이름을 더합니다. 옛 이름은 폐기 예고만 해 둡니다. 실제로 없애는 일은 다음 메이저 때 한꺼번에 합니다.
거꾸로 브레이킹 체인지가 없어도 메이저를 올리는 프로젝트도 있습니다. 출시 일정이나 큰 기능 소개에 맞춰 맨 앞 숫자를 올리는 경우입니다. 그런 프로젝트에서는 메이저 숫자가 깨짐을 뜻하지 않으므로, 그 프로젝트가 번호를 어떤 규칙으로 붙이는지 먼저 확인합니다.
관련 항목
메이저 버전과 함께 버전 번호를 나누는 칸
마이너 릴리스 · 패치 릴리스 · 프리릴리스 · 릴리스 · 빌드 메타데이터
메이저 버전을 올리게 하는 변경과 성질
하위 호환 · 하위 비호환 변경 · 브레이킹 체인지 · 공개 API · 상위 호환 · 안정성 수준
메이저 버전을 정하는 번호 규칙
시맨틱 버저닝 · 버저닝 · 캘린더 버저닝 · 버전 번호
웹 API 에서 메이저 버전을 가르는 방법
API 버전 관리 · URL · 헤더 · 콘텐츠 협상 · REST API · 엔드포인트
옛 메이저를 정리하는 단계
폐기 예고 · 마이그레이션 가이드 · 장기 지원 · 지원 종료 · 변경 로그 · 릴리스 노트
메이저 버전을 걸러 받는 도구와 장치
패키지 매니저 · 의존성 · 버전 범위 · 락 파일 · 의존성 관리
메이저가 엇갈려 터지는 문제
의존성 지옥 · 다이아몬드 의존성 · 버전 충돌 · 의존성 해석
메이저 버전이 속하는 상위 분야
다른 이름: major version · 메이저 릴리스 · major release · 주 버전 · 메이저 번호