가장 쉬운 🙈 유의적 버전 semver

 

현대의 프로그램은 많고 복잡한 의존 관계를 가지고 있습니다. 만약 의존하고 있던 프로그램의 버전이 업데이트 되면서 문제가 생기면 대응을 해야하는데, 의존하는 라이브러리가 수십개에다가, 모든 라이브러리가 잦은 업데이트를 한다면, 개발은 커녕 업데이트에 대응하느라 시간을 모두 써야 할 것입니다. 이럴 때, 대상 라이브러리의 버전을 지정하여 효율적으로 관리해줄 수 있게 도와주는 버전 관리 방식 중 대표적인 방법이 '유의미 버전' 으로 번역되는 semantic versioning(줄여서 semver) 입니다.

 

https://semver.org/lang/ko/

 

이 글에서는 유의적 버전이 어떻게 작동하는지 스토리텔링 방식으로 풀어보며, 각 버전 변경의 대표적인 사례를 함께 살펴 보고자 합니다. 이 글을 한 번 읽어본 뒤, 위 링크의 semver 공식문서를 보신다면 훨씬 이해가 쉬우실 거라 생각합니다.

semver 의 구성

semver 는 . 으로 구분된 3개의 숫자로 구성됩니다. 왼쪽부터 순서대로 Major 버전, Minor 버전, Patch 버전이라고 부릅니다.

semver 의 구성 Major, Minor, Patch 버전

 

Breaking Change

breaking change 란, 패키지를 활용하는 방법(스펙)에 변화가 생겨 그 패키지를 의존하는 패키지가 업데이트에 대응해야 하는 변경을 의미합니다.

 

Speaker 라는 패키지가 있고, YouBand 라는 패키지가 Speaker 패키지를 의존한다고 생각해봅시다. Speaker 패키지는  musicOn(musicName, volume) 이라는 스펙을 갖는 함수를 제공합니다.

package Speaker@v1

turnOnMusic(musicName, volume) {
  // drop the beat!
}

 

이 패키지를 의존하는 다른 패키지 YouBand 는 musicOn 을 의존하여 musicOn 함수를 사용합니다.

package YouBand

import Speaker@v1

Speaker.turnOnMusic('Yesterday', 10)

 

어느 날, Speaker 패키지가 버전이 업데이트되면서 musicOn 의 두 번째 인자로 제공하던 volume 을 삭제하는 업데이트를 하게 됩니다.

package Speaker@v2

turnOnMusic(musicName) {
  // drop the beat!
}

 

만약 YouBand 패키지가 Speaker 패키지를 업데이트하고 turnOnMusic 함수를 호출하는 부분의 코드를 수정하지 않는다면 문제가 발생할 수 있습니다.

package YouBand

import Speaker@v2

Speaker.turnOnMusic('Yesterday', 10) // Erorr Occurred!

 

semver 를 지키는 패키지라면, 의존하고 있는 패키지에게 자신의 업데이트로 인해 "부서질 수도 있음"을 알릴 방법이 필요합니다. 그것이 바로 Major 버전을 바꾼다는 것의 의미입니다.

 

 

Major Version 의 변경

semver Major 버전 변경의 의미

semver 에서만큼은 이번 업데이트로 의존하고 있는 패키지가 제대로 작동하지 않을 수도 있다고 판단될 때 Major 버전을 변경합니다. 그래서 Major 버전의 변경은 어떻게 대응하면 되는지 Migration Guide 가 함께 제공되거나, 어떤 부분이 변경되어 Breaking Change 가 되었는지 알림으로써, 의존하는 패키지가 Major 버전을 업데이트하면서 수정해야 하는 부분을 명확히 알립니다.

 

semver 를 지킨다면, 이와 같이 Major버전의 업데이트에서는 하위호환성(backward compatibility)이 보장되지 않으므로, 의존성을 업데이트하는 패키지의 관리자라면 주의해야 합니다. 

Minor Version 의 변경

semver Minor 버전 변경의 의미: 기능의 추가
semver Minor 버전 변경의 의미

 

위 Speaker 와 YouBand 패키지의 예제에서, Speaker 가 turnOnMetronome(bpm) 스펙을 갖는 함수를 '추가'했다고 생각해봅시다. 이 업데이트는 패키지를 의존하는 패키지에게 추가 기능만을 제공할 뿐, 기존 기능의 사용에는 영향을 주지 않습니다. (그래서, breaking change 가 포함되는 업데이트가 minor 버전의 업데이트에 포함되면 안됩니다.)

 

semver 를 지킨다면, 이와 같이 Minor 버전의 업데이트에서는 하위호환성(backward compatibility)이 보장되어야 합니다.

Patch Version 의 변경

semver Patch 버전 변경의 의미 : 버그 수정
semver Patch 버전 변경의 의미

기존 제공하던 패키지에 오류, 버그, 보안 취약점 등이 발견되었다면, 이는 즉시 업데이트되어야 합니다. 이러한 변경이 포함되는 버전 업데이트는 Patch 버전으로 명시합니다.

 

semver 를 지킨다면, Patch 버전의 업데이트에서는 하위호환성(backward compatibility)이 보장되어야 합니다.

 

semver 는 가이드일 뿐

semver 는 의존관계 속에서 패키지 사이의 버전 관계를 제어하는 유용한 가이드이지만, 이것이 꼭 따라야만 하는 표준인 것은 아닙니다.

semver 를 따르지 않는 프로젝트

라이브러리의 형태가 아닌 대체적인 프로덕트들은 자신만의 버저닝 규칙을 갖고 있기도 합니다. 하지만, 라이브러리임에도 

semver 를 따르지 않는 대표적인 패키지가 있는데요. 그건 (의의로) typescript 입니다.

 

TypeScript does not adhere to Semantic Versioning

https://www.semver-ts.org/1-background.html

 

4.3 버전에서 4.4 버전으로(4.4 는 Beta, 4.4.2 가 4.4 의 정식 릴리즈)의 업데이트에서 typescript 는 any 였던 catch 의 인자 타입을 unknown 으로 변경합니다. 이는 의존하는 패키지들의 소스코드를 변경해야 하는 업데이트를 하게 했습니다. semver 규칙을 따른다고 보면 Minor 버전의 업데이트였지만, Breaking Change 를 포함한 업데이트였던 것입니다. (NestJS 는 이 업데이트로 tsconfig 를 수정함) 그렇다고 typescript 를 비난할 수는 없습니다. 단지 typescript 가 semver 를 따르지 않을 뿐입니다. 

 

semver-ts 는 typescript 가 semver 를 온전히 따르지 않기 때문에, typescript 를 의존하는 패키지에서 breaking change 에 대응할 수 있는 방법을 나름 제안하는 문서입니다.

더보기: typescript 버저닝 규칙

https://www.learningtypescript.com/articles/why-typescript-doesnt-follow-strict-semantic-versioning#typescript-versioning-today

semver 를 따르는 패키지

npm 에 많은 패키지들이 semver 를 따릅니다. 그 중에서도 대표적으로 react 는 semver 를 따릅니다. Breaking Changes 는 Major 버전의 업데이트에만 포함되어 있는 것을 github release note 에서 확인할 수 있습니다.

반응형

Designed by JB FACTORY