API 하위 호환성을 어떻게 지킬 것인가??

2023. 11. 19. 18:18우아한 테크코스

728x90
반응형

디깅룸이라는 안드로이드 어플을 7월 달에 어플을 출시하고 지금까지 쭉 운영하고 있습니다. 디깅룸은 사용자의 음악 취향 정보를 수집해 취향에 맞는 음악/영상을 추천해주는 어플입니다. 우아한테크코스 과정 중에 프로젝트로 진행했던 서비스인데요. 우아한테크코스 과정이 끝나더라도 지속적으로 기능을 개선하고 운영해가기로 결정했으니 다들 한번 사용해보세요!

 

 

API 버저닝은 왜 필요할까?


안드로이드 팀원들과 어플을 개발하는 경우에는 항상 주의해야하는 점이 있습니다. 그것은 바로 API의 하위호환성을 고려해서 배포를 해야한다는 점입니다. api가 변경될 때마다 사용자에게 어플을 강제로 업데이트를 시킬 수는 없는 노릇이니까요. 

 

저희는 이 부분을 놓치고 있었습니다. 처음 어플을 출시할 때는 회원 가입 기능이 있었는데요. 다음과 같은 이유 때문에 회원가입 기능을 없애기로 결정했습니다.

 

(1) 회원가입 과정에서 수집한 사용자 정보를 음악 추천 알고리즘에서 사용하지 않는다.

(2) 불필요한 회원가입 과정이 서비스 이용의 진입 장벽이 된다.

 

 

사실 저희 서비스는 이윤을 창출하기 위한 서비스가 아니라 프로젝트성이 짙은 서비스입니다. 저희가 프로젝트를 통해 달성하고자했던 최종적인 목표는 '사용자 유치'였습니다. 실질적으로 회원 데이터를 이윤 창출에 사용하지 않을 예정이었고, 오히려 회원가입 기능은 진입장벽에 불과하다고 판단해 이를 없앴습니다. 그래서 회원가입 관련 기능을 제거한 뒤 서버에 다시 배포했죠. 물론 어플 또한 회원가입 기능을 제거하고 플레이 스토어에 배포했습니다. 그리고 며칠 뒤에 실 사용자로부터 피드백을 받았습니다.

 

'어플 회원가입 기능이 동작하지 않아요.'


 

문제가 발생했습니다. 바로 한 사용자가 어플을 다운받고 며칠이 지난 다음에 어플에 들어가서 서비스를 이용하려고 했던 것이었습니다. 저희는 급하게 회원가입 기능을 다시 살려내고 재배포를 했습니다. 그리고 API 하위 호환성에 관해 이야기를 나눴습니다.

 

 

API의 하위 호환성을 보장할 것인가? 아니면 API 변경이 있을 때마다 강제 업데이트를 할 것인가?


 

이 부분에 대해서는 모든 팀원들이 하위 호환성을 보장하는 것으로 의견을 모았습니다. API 변경이 있을 때마다 강제로 업데이트한다는 것은 사용자가 떠나갈 수 있는 요인으로 작용할 수 있겠다고 판단했거든요. 사실 당장 저희만 하더라도 자주 사용하고 있는 배달의 민족, 유튜브, 네이버와 같은 어플은 대부분 업데이트하지 않잖아요? 혹은 어플을 처음 들어갔을 때 업데이트를 할지 말지 정하는 문구가 뜰 때는 항상 '나중에'라는 버튼을 눌러 업데이트를 해야하는 시기를 늦추곤하죠.

 

특히나 저희 서비스의 주요 타겟층이 저희 서비스를 이용할 것으로 예상하는 시간대는 출퇴근 시간, 조금 더 구체적으로는 대중교통을 이용할 때입니다. 이건 굉장히 주요한 시사점을 안겨주는데요. 대중교통을 이용할 때는 데이터를 소모해야하기 때문에 어플을 업데이트하는 과정이 부담스럽게 여겨질 수 있거든요.

 

결국 강제로 사용자에게 업데이트를 요구하는 작업은 서비스를 이용하지 않도록하는 넛지로서 작용할 수 있다고 생각했습니다. 따라서 강제 업데이트는 최대한 지양하는 것을 목표로 하고, 최대한 API 하위 호환성을 보장하는 것을 목표로 잡기로 했습니다. 

 

 

 

어떻게 API 하위 호환성을 보장할 것인가?


 

 

그렇다면 API 하위 호환성을 어떻게 보장할 수 있을까요? API 하위 호환성을 보장하기로 한 시점부터는 이를 '잘 적용하기 위해' 고민해야하는 사항들이 많습니다. 우선 API 하위 호환성을 보장하기 위해 저희는 시멘틱 버저닝을 적용하기로 결정했습니다.

 

API 시멘틱 버저닝을 사용하자!


 

출처: Geeks For Geeks

 

아마 시멘틱 버저닝을 들어보신 분들이 계실겁니다. 간단하게 설명드리자면 API 하위 호환성을 보장하지 못하는 변경사항이 있는 경우에는 Major Version을 하나 올립니다. API 하위 호환성을 보장하면서 API 기능이 추가된 경우에는 Minor Version을 하나 올립니다. 간단하게 버그를 수정한 경우에는 Patches를 하나 올립니다. 찾아보니 시멘틱 버저닝 문서도 있더라고요. 이에 대해서 궁금하신 분들을 한번 들어가서 확인해보셔도 좋을 것 같네요!

 

저희는 시멘틱 버저닝을 적용해서 API 하위 호환성을 지키고자 했습니다. 다만 프로젝트 초기이기에 API 변경이 잦은 만큼 Major Version이 자주 바뀔 것으로 예상했습니다. 그래서 Major Version은 서비스 전반에 걸친 변화가 있는 경우에 올려주기로 결정하고, API 하위 호환성을 지킬 수 없는 변경 사항이 생기는 경우에는 Minor Version을 올려주기로 결정했습니다. 그리고 버그 수정과 API 하위 호환성을 보장하는 기능 확장은 Patches를 올려주기로 결정했습니다. 

 

앞으로 이 버전에 대한 표현이 자주 등장할 예정이니 어떤 변화가 있을 때 어떤 버전을 올려주기로 했는지 잘 짚고 글을 계속 읽어 나가시면 좋겠네요!

 

 

 

API 버전 관리는 어디서 할 것인가?


 

두번째로는 API 버전을 어디에서 관리할 지에 대해 결정해야했습니다. 사실 상 API 버저닝 정책 상 가장 중요한 부분이라고 생각하는데요. 우선 저희가 고민했던 몇가지 후보를 먼저 설명드리도록 할게요.

 

1. 어플리케이션 단에서 애너테이션을 사용해 버전 관리를 진행한다.

2. 어플리케이션 단에서 url을 사용해 버전 관리를 진행한다.

3. 어플리케이션 단에서 버전을 명시하고 웹서버에서 버전 관리를 진행한다.

 

 

1. 어플리케이션 단에서 애너테이션을 사용해 버전 관리를 진행한다.

먼저 어플리케이션 단에서 애너테이션을 사용해 버전을 관리하는 방안입니다. 간단하게 예시로 한번 살펴보시죠.

핸들러에서 사용할 ApiVersion 애너테이션

 

우선 각 핸들러에서 사용할 ApiVersion 커스텀 애너테이션을 생성합니다. 그리고 이를 각각의 핸들러 위에다가 명시해주는 것이죠.

 

구버전 api

 

아까 위에서 언급했던 대로 저희는 회원 가입을 지원하지 않기 때문에 일반 로그인을 사용하는 API는 구 버전인 '1.0.0' 버전으로 남게 됩니다.

 

신버전 api

 

신 버전에서 사용할 API는 '1.1.0'임을 명시해줄 수 있죠. 참고로 게스트 로그인은 기능이 확장된 것이기 때문에 Pathes를 올리는 것이 맞지 않나?라고 하실 수 있겠지만 현재는 기존 API를 삭제해서 Minor Version이 올라간 상황임을 인지해주세요.

 

여러 버전에서 사용하는 api

 

 

여러 버전에서 사용하는 api는 version을 계속 나열해서 명시해줄 수 있습니다.

 

그리고 HandlerMapping을 커스텀해서 ApiVersion에 따라 핸들러 매핑이 각각 다르게 되도록 구현할 수 있습니다. 해당 방법에 대해서는 사실 우테코 '이돈이면' 팀에서 적용한 API Versioning을 보고 이렇게도 적용할 수 있겠구나~ 깨달았던 부분이기에 자세한 코드는 이돈이면 버저닝 코드를 확인하시면 좋을 것 같네요.

 

이 방법은 소스 코드에서 커스텀 애너테이션을 만들어서 버저닝을 진행할 수 있기 때문에 핸들러별로 어떤 버전에서 사용 중인지 직관적으로 확인할 수 있다는 장점이 있습니다. 이돈이면 팀이 아주 예쁘게 코드를 작성해놨기 때문에 참고할 수 있는 레퍼런스 코드가 있다는 것도(땡큐) 정말 큰 장점 중 하나였죠. 

 

반면 버저닝이 지속될 수록 여러 버전에서 사용되는 핸들러는 정확히 어떤 버전이 사용되는지 확인하기 어려워진다는 점과 버전이 업 될 때마다 수작업으로 핸들러마다 버전을 명시해야한다는 점. 그리고 기존 코드가 모두 남아있기 때문에 유지보수가 어려워진다는 점의 단점도 존재했습니다. 보기 편하게 정리하자면,

 

 

장점

- 버저닝 업그레이드가 많이 발생하지 않으면 직관적으로 핸들러별 버전을 확인할 수 있다.

- 이돈이면 팀이 잘 작성해놓은 레퍼런스가 있어서 참고해서 빠르게 작성할 수 있다.

- 버전을 잘못 작성한 핸들러에 대해서만 예외가 발생하고 다른 핸들러는 정상 작동한다.

 

단점

- 버저닝이 자주 업그레이드되면 관리하기 어렵다.

- 매번 담당자가 버전을 명시해야해서 휴먼 에러에 취약하다.

- 이전 버전의 코드를 남겨야해서 컨트롤러부터 도메인 영역까지 영향을 미치게 된다.

 

 

API 버저닝 업그레이드가 많이 발생하지 않는다면 위의 방법도 좋은 방법이라고 생각합니다. 빠르게 버저닝을 적용할 수 있다고 생각하기 때문이죠. 다만, 저희 디깅룸에서는 API 변경과 기능의 수정/삭제도 종종 발생하던 상황이었기에 버전 업그레이드가 잦을 것이라 생각했습니다. 그래서 시멘틱 버저닝에서 Major Version을 저희는 Minor Version으로 사용하기로 결정하기도 했고요. 또한 이전 버전의 코드를 남겨둬야하는데, 이 과정에서 컨트롤러 뿐만 아니라 도메인 영역까지 영향을 미친다는 부분도 유지보수의 어려움을 낳을 수 있겠다고 생각했습니다. 

 

Password는 1.0.0 버전에서만 활용하고 이후에는 활용하지 않습니다.

 

위의 예시처럼 버전이 업그레이드되더라도 하위 호환성을 보장하기 위해 도메인 영역까지 이전 버전의 코드를 남겨둬야한다는 점이 버전이 매번 업그레이드될 때마다 유지보수의 어려움을 야기할 것으로 예상했습니다. 따라서 저희는 1번은 사용하지 않기로 결정했습니다.

 

 

2. 어플리케이션 단에서 url을 사용해 버전 관리를 진행한다.

두번째 방안인데요. url을 통해서 버전 관리를 진행하는 방식입니다. 기존 버전을 v1버전, 이후 버전을 v2버전 이런 방식으로 엔드포인트를 설정해서 요청을 전달받는 방법입니다.

 

구버전 api

 

신버전 api

 

 

업데이트된 기능에 문제가 발생했을 때 이전 버전으로 api만 바꾸면 되기 때문에 문제 상황의 대처가 무척 간단하다는 장점이 있습니다. 바로 엔드포인트만 변경해서 요청을 보내면 되니까요. 그리고 어플리케이션 레벨에서의 버전을 설정하지 않고 핸들러마다 버전을 설정할 수 있다는 점도 장점일 수 있겠네요. 버전 업그레이드를 할 핸들러만 설정해서 업그레이드를 하면 되니까요. 그래서 가독성도 좋고, 아무래도 휴먼 에러에는 덜 취약하다는 장점도 있겠습니다. 업그레이드된 버전만 처리해주면 되기 때문이죠.

 

반면에 이 또한 기존 코드가 남아있어 유지보수의 어려움이 있다는 단점이 있습니다. 그리고 저희는 '안드로이드'와 협업을 하기 때문에 앞서 언급한 업데이트된 기능에 문제가 발생했을 때 롤백이 간단하다는 장점 또한 제대로 누릴 수 없습니다. 서버에서 생성한 기능에 문제가 발생하면 요청하는 api를 변경해야하는데, api를 변경하기 위해서는 플레이 스토어에 재배포를 해야하기 때문에 시간이 조금 발생하기 때문이죠. 그리고 그 문제가 생긴 버전을 업그레이드 한 사람들에게도 강제 업데이트를 해야합니다. 그럴바에는 그냥 서버에서 재배포하는 게 더 빠를 수도 있죠. 이 또한 요약해보자면

 

장점

- 업데이트된 기능에 문제가 있으면 빠르게 이전 버전 기능으로 옮겨서 사용할 수 있다.

- 핸들러마다 버전을 설정해 가독성도 좋고, 휴먼 에러에는 덜 취약하다.

 

단점

- 기존 코드가 남아있어 유지보수의 어려움이 있다.

- 안드로이드와 협업을 하기 때문에 업데이트된 기능에 문제가 있으면 이전 버전 기능으로 빠르게 대체한다는 장점을 활용하기 어렵다.

 

해당 방식은 문제가 발생했을 때의 대처를 빠르게 할 수 있다는 장점도 없어졌고, 유지보수의 어려움이 여전히 존재한다는 단점이 있기 때문에 사용하지 않기로 결정했습니다.

 

 

 

3. 어플리케이션 단에서 버전을 명시하고 웹서버에서 버전 관리를 진행한다. (선택)

서버를 띄울 때 해당 서버가 몇 버전인지를 명시하고, 웹서버에서는 요청에서 버전을 파싱해 가져온 뒤 그에 맞는 서버로 요청을 전달하는 방식입니다.

 

일단 해당 방법의 예상되는 장단점부터 먼저 설명드려보겠습니다. 먼저 장점은 버전이 업그레이드되더라도 기존 코드를 없앨 수 있기 때문에 유지보수가 간단해진다는 점이 있습니다. 또한 업그레이드된 버전에 문제가 발생하는 경우에는 업그레이드된 버전에만 문제가 있기 때문에 이전 버전의 유저들은 여전히 잘 사용할 수 있다는 장점이 있습니다.

 

하지만 관련 자료가 없기 때문에 해당 방법이 구현 가능한지 한번 따져봐야했고, 가능하더라도 조금 오랜 시간이 걸릴 것으로 판단했습니다. 또한 휴먼 에러에 취약하다고 판단이 서기도 했습니다. 그리고 현재 브랜치 전략에 의해 이전 Minor 버전과 Major 버전의 업그레이드를 지속할 수 없다는 문제도 있습니다. 이는 차차 설명드리도록 하겠습니다. 아! 하나의 인스턴스에서 여러 대의 서버를 띄워야한다는 점도 인스턴스 입장에서는 부담이 될 수 있겠네요.

 

이 또한 장단점을 정리해보자면

 

장점

- 이전 버전의 코드가 남아있지 않아 유지보수가 간단해진다.

- 업그레이드된 버전의 서버가 문제가 발생해도 이전 서버 사용자는 문제없이 사용할 수 있다.

 

단점

- 구현 가능성을 판단하기 어려웠다.

- 휴먼 에러에 취약하다.

- 현재 브랜치 전략에 의하면 이전 Minor 버전, Major 버전의 Pathes 업그레이드를 할 수 없다.

- 하나의 인스턴스에 여러 대의 서버를 띄워서 관리해야하기에 부담이 될 수 있다.

 

 

저희는 일단 세 가지 방법 중 해당 방법이 가장 이상적이라 생각했습니다. 왜냐하면 이전 버전의 코드가 남아있지 않아 유지보수가 간단해진다는 장점이 너무 크게 다가왔거든요. 서비스의 규모가 클 수록 유지보수하기 좋은 코드의 중요성이 점점 더 커질 것으로 예상하는데 이러한 측면에서는 굉장히 큰 이점이 될 것으로 예상했습니다. 

 

다만, 이를 위해 해결해야하는 과제들이 남아있죠. 여러 단점들이 존재하기 때문에 이를 보완할 전략들이 필요했습니다. 

 

 

어떻게 API 버전 관리를 할 것인가?


우선 구현 가능성입니다. 가장 먼저 어떤 액션이 필요한지 선정해놓고, 이후에 각 액션을 구현하기 위해 어떤 기술적인 내용들이 들어갈 수 있을지 고민했습니다. 그래서 다음과 같은 액션들을 고려해보았습니다.

 

- 각 서버에서는 본인이 몇 버전인지 알 수 있다.

- 클라이언트는 어떤 버전에 요청을 보낼 지 버전 정보를 전달한다.

- 웹서버는 버전 정보를 파싱해 각 버전에 맞는 웹 서버로 요청을 보낸다.

 

 

그리고 어떤 기술들로 이를 해결할 수 있을지 논의해보았습니다. 

 

(1) 각 서버에서는 본인이 몇 버전인지 알 수 있다.

-> 스프링 액츄에이터를 사용해 버전을 공개한다.

 

(2) 클라이언트는 어떤 버전에 요청을 보낼 지 버전 정보를 전달한다.

-> header나 url로 버전 정보를 전달할 수 있다.

 

(3) 웹서버는 버전 정보를 파싱해 각 버전에 맞는 웹 서버로 요청을 보낸다.

-> nginx의 conf 파일에서 if나 map을 사용해 로드 밸런싱을 할 수 있다. 또한 header나 url 정보를 파싱할 수 있다.

 

일단 해당 방법으로 구현가능하다는 것을 깨닫고 본격적으로 구현에 돌입했습니다.

 

 

(1) 각 서버에서는 본인이 몇 버전인지 알 수 있다.

우선 스프링 액츄에이터를 사용해 서버의 상태를 공개할 수 있도록 했습니다.

 

 

그리고 application.yml에 version에 관한 내용을 입력해 해당 서버에서 버전을 공개할 수 있도록 했습니다.

도메인/example/info로 접속하면 버전 확인이 가능

 

그러면 위와 같이 버전을 확인할 수 있는 것이죠.

 

 

(2) 클라이언트는 어떤 버전에 요청을 보낼 지 버전 정보를 전달한다.

 

header와 url로 버전 정보를 전달받는 방법 중에는 header로 정보를 전달받기로 결정했습니다. url은 버전이 증가할 때마다 안드로이드가 api 통신을 진행할 때 모든 요청에 대한 url을 수정해야한다는 단점이 있습니다. 반면 header 정보는 현재 서버의 버전에 대해 전역적으로 처리하면 되기 때문에 더 편리하다는 장점이 있습니다. 그래서 header를 사용해 버전 정보를 전달받기로 결정했습니다.

 

그러면 웹서버에서 header 정보를 파싱할 수 있는지도 확인해보아야겠죠.

 

 

(3) 웹서버는 버전 정보를 파싱해 각 버전에 맞는 웹 서버로 요청을 보낸다.

저희는 웹서버에서는 nginx를 사용했는데요. nginx에서는 '$http_'라는 prefix를 붙이면 간단하게 헤더 정보를 가져올 수 있습니다. 또한 if나 map을 사용해 요청을 전달할 수도 있죠.

 

nginx의 .conf 파일

 

위와 같이 명시를 하게되면 버전 정보를 파싱하여 웹서버를 매핑할 수 있습니다. 버전이 없는 경우에는 일단 1.0.0 버전의 서버로 요청이 전달되도록 했습니다. 이전에 안드로이드 어플을 다운로드받은 사용자는 버전이 없기 때문에 해당 요청들도 모두 받을 수 있게 하기 위해서죠. 

 

 

그래서 아래와 같이 API 하위 호환성을 보장할 수 있는 것이죠!

 

version 1.0.0 요청이 들어오면 1.0.0 버전의 서버로 요청 전달

 

version 1.1.0 요청이 들어오면 1.1.0 버전의 서버로 요청 전달

 

버전이 없는 경우에는 1.0.0 버전 서버로 요청 전달

 

 

아직까지 해결하지 못한 과제들


그리고 아직 남은 과제들이 있습니다. 사실 이 과제들에 어떻게 해야할지 아직 고민 중에 있는데요. 현재 고민 중인 내용들만 공유해드리도록 하겠습니다.

 

'휴먼 에러에 취약하다.'


우선 위의 방식은 개발자가 yml 파일에 직접 버전을 명시해야합니다. 당연한게 아닌가 할 수 있지만 실수를 했을 때의 파급 효과가 조금 크다는 점이 큰 단점으로 작용합니다. 버전 업그레이드를 잘못한 경우를 한번 가정해볼게요.

 

 

 

처음에는 하나의 서버만 띄워져있을거예요. 위의 예시로 가정한다면 아직 로그인 기능과 회원가입 기능이 존재하겠죠. 

 

버전 업그레이드를 올바르게 한 경우에는 8081 포트에 1.1.0 버전의 서버가 올라가게 됩니다. 이 덕분에 버전별로 사용자의 요청을 버전별로 전달받을 수 있게 되죠.

 

 

하지만 개발자가 실수로 버전 업그레이드를 하지 않은 채로 배포를 하게되면 1.0.0 버전 서버에 덮어쓰기가 됩니다. 저희 서비스에서 로그인 기능은 사라지게 되는 것이죠. 결국 휴먼 에러가 서비스 전반에도 영향을 미치게 됩니다. 정말 큰 문제인 것이죠. 그래서 서비스 규모가 커지기 전에 이러한 문제를 방지하는 것이 큰 과제라고 생각합니다.

 

이를 어떻게 해결할지 고민 중인데요. 우선 서버의 빈으로 등록된 핸들러의 정보만 알 수 있다면 개발자가 버전을 잘못 입력했을 때 배포에 실패하도록 할 수 있을 것이라 생각해보았습니다. 다음과 같은 예시를 한번 살펴볼게요.

 

버전 업그레이드를 잘 한 경우

 

기존 api가 삭제되거나 변경되는 경우에는 Major 버전과 Minor 버전이 업그레이드됩니다. 위의 경우처럼 말이죠.

 

버전 업그레이드를 하지 않아 빌드나 배포가 실패하는 상황

 

 

하지만 api가 변경되어서 버전 업그레이드를 해야하는데 버전 업그레이드를 하지 않는 경우에는 배포/빌드가 강제로 실패하도록 한다면 휴먼 에러에 취약하다는 단점을 해결할 수 있을 것으로 보았습니다. 어느 시점에서 이를 실패하도록 할지는 고민 중에 있습니다.

 

현재 두 가지 방법을 고민 중인데요. flyway 처럼 버전 별로 핸들러 문서를 만들어서 관리하는 방법과 액츄에이터에서 핸들러 리스트를 파악해 배포 시점에서 이를 확인하는 방법입니다. 어떤 방법을 사용하느냐에 따라 버전이 잘못되었을 때 빌드 시점에서 실패를 할지, 배포 시점에서 실패를 할지를 결정할 수 있겠네요.

 

글을 작성하면서 확인해보니 액츄에이터는 '/mappings'라는 엔드포인트를 열면 핸들러 목록을 확인할 수 있더라고요. 이 방법을 사용하면 조금 편하게 구현은 할수야 있겠지만 외부에 핸들러를 공개하는 것에 부담이 있어 이는 조금 더 고민해보아야할 것 같습니다.

 

 

'현재 브랜치 전략에 의하면 이전 Minor 버전, Major 버전의 Pathes 업그레이드를 할 수 없다.'


 

현재 저희는 백엔드와 안드로이드 파트가 모두 하나의 깃허브 레포지토리에서 관리되고 있습니다. 효율적으로 이를 관리하기 위해 main, android/backend, feature 브랜치로 나눠서 관리하고 있습니다. 백엔드 크루들의 입장에서 각 브랜치의 역할은 다음과 같습니다.

 

- main: production server에 배포할 때 사용하는 브랜치

- backend: develop server에 배포할 때 사용하는 브랜치

- feature: 기능을 개발할 때 사용하는 브랜치

 

그래서 현재 브랜치 전략을 따르면서 개발을 하게 되면 다음과 같은 개발 흐름이 진행됩니다.

점점 버전 업그레이드가 됩니다.

 

1.0.0 버전에서 1.0.1 버전으로, 1.0.1 버전에서 1.1.0 버전으로 업그레이드가 됩니다. 겉으로 보기에는 큰 문제가 없어보이죠. 버전이 업그레이드되는 것은 자연스러운 현상이니까요. 그러나 1.1.0 버전으로 업그레이드를 한 이후에는 1.0.x 버전에 대한 업그레이드가 불가능합니다. main 브랜치 하나에서만 작업을 하기 때문에 중간에 1.0.1 버전에 대한 브랜치를 따로 만들어서 merge를 시키게 되면 1.1.0 버전과 충돌이 발생할 수 있고 여러 버전이 하나의 브랜치에서 섞여버린다는 문제가 발생합니다. 

 

따라서 이러한 문제를 해결하기 위해 버전별로 브랜치를 만들어볼 수 있습니다. 

 

그리고 각 버전별 브랜치에 merge가 발생할 경우에는 배포 자동화를 진행하면 버전 충돌 없이 이전 버전들도 업그레이드가 가능합니다. 단 이때 몇 가지 조건들도 수반되어야겠죠.

 

- 업그레이드된 Major 버전이나 Minor 버전을 만들 때는 바로 직전 버전의 브랜치를 main에 merge한 이후에 진행한다. (상황에 따라 main에 merge하는 브랜치가 달라질 수 있음)

- 업그레이드된 Major 버전이나 Minor 버전을 만들 때를 제외하곤 main 브랜치에 merge하지 않는다. 

- main 브랜치는 production server에 배포를 위해 사용하는 브랜치가 아닌 버전 업그레이드를 위해 사용한다.

 

등등... 간단하게만 생각해보아도 이렇게 지켜야할 규칙들이 많아지죠. 사실 브랜치 전략은 팀원들 모두가 공유하는 전략이기 때문에 복잡하지 않고, 간단하게 이뤄져야한다고 생각하는데요. 위의 방식은 너무 복잡하고 버전 업그레이드가 많이 이뤄질수록 관리가 어려워질 것으로 보입니다. 

 

이런저런 문제들 때문에 저희는 이러한 전략을 도입하지 않을 것 같습니다. 선택을 할 때는 항상 상황과 비용을 고민해야하는데요. 현재 공식적인 프로젝트 기간이 마무리되었고, 자발적으로 프로젝트를 운영하고 있는 기간입니다. 팀원들 모두 취업 준비를 하고 있는 상황에 앞으로 프로젝트에 얼마나 시간을 투자할 수 있을지 불투명한 상황입니다. 더군다나 4-5개월동안 다른 브랜치 전략을 사용하다가 해당 브랜치 전략으로 바꾸게 되면 이해하고 적용하는데 시간과 비용이 많이 증가할 것으로 예상됩니다. 이러한 관점에서 보았을 때 위와 같이 브랜치 전략으로 바꿔서 얻게될 이득보다는 비용이 훨씬 더 클 것으로 예상되기에 해당 전략은 적용하지는 않을 것 같습니다. 아직 팀원들과 해당 부분에 대해 논의하지 않았기에 결정된 사항은 없어서 일단 고민했던 내용을 이 정도로만 공유해드리겠습니다.

 

 

 

'하나의 인스턴스에 여러 대의 서버를 띄워서 관리해야하기에 부담이 될 수 있다.'


이 부분에 대한 해결책은 답이 정해져있다고 생각합니다. 바로 scale up과 scale out이죠. 버전이 계속 업그레이드된다면 여러 서버가 띄워지게 될텐데 사양이 좋은 인스턴스를 쓰거나 여러 인스턴스를 쓰는 방법을 고려해볼 수 있겠네요. 

 

현재는 깃허브 액션을 활용해서 배포 자동화를 하고 있는데요. scale out을 하게 되면 배포 서버를 만들어서 관리하는 것도 좋겠네요.

 

물론... 이 또한 지금은 고려하지 않으려고 합니다. 사용자가 많이 늘고 있는 시점에서 서버가 견디기 힘들 때 scale out이나 scale up을 고려해야한다고 생각합니다. 하지만 현재 MAU를 보았을 때 아직은 위의 방안을 고려할만한 시점은 아니라고 생각합니다. 좋은 거야 물론 알지만 항상 비용을 고려해야하니까요.

 

 

 

글을 적다보니 생각난 또 다른 단점도 존재하는데요. 현재 이 방식은 모놀리딕 아키텍쳐에 의존적인 API 하위 호환성인 것 같다는 생각이 떠오르기도 하네요. MSA에 대해 잘은 모르지만 서비스별로 인스턴스를 띄운다고 하면 어플리케이션 단위로 버전을 관리하는 것보단 api 별로 버전을 관리하는 것이 더 유리해보이네요. MSA는 정말로 잘 몰라서 이러한 문제가 있을 것 같다는 생각만 남기면서 넘어가도록 하겠습니다. 

 

 

그 외의 고민해본 문제: 엔티티와 데이터베이스의 스키마가 달라질 경우에 어떻게 될 것인가?


위의 문제에 대해 이야기를 나눠본 적이 있습니다. 이 부분은 실제 실험을 해보았는데요. 결론부터 말씀드리자면 일단 현재로서는 문제가 발생하지 않을 것으로 결론이 나왔습니다.

 

다만 다음과 같은 조건이 필요합니다.

- 새로운 컬럼이 추가될 때는 null을 허용한다.

- 기존 컬럼이 필요 없어지는 경우에는 'null을 허용'하는 제약조건을 추가한다.

 

즉, null을 허용하는 식으로 위의 문제를 해결할 수 있습니다. DB에는 컬럼이 있고, 엔티티에는 매핑되는 필드가 없는 경우에는 예외가 발생하지 않더라고요. 그래서 API가 변경되었을 때나 API가 삭제되었을 때를 가정하고 실험을 해보았을 때 문제가 발생하지 않는다는 결론이 나왔습니다. 아무튼 DB에는 모든 버전에 대한 컬럼이 있을 테니까요.

 


일단 이렇게 현재 API 하위 호환성을 보장해주었습니다. 아직 해결하지 못한 과제들도 있지만 현재 팀원들의 상황과 비용을 고려해서 적용할 것들가 적용하지 않을 것들을 분리해서 우선순위에 따라 차근차근 적용해보려고 합니다. 원래는 무중단 배포도 엮어서 글을 써보려고 했는데요. 이미 API 하위 호환성만으로도 글이 너무 길어져서 이번 포스팅은 여기서 마무리해보도록 하겠습니다. 무중단 배포는 간단하게 구현해서 포스팅이 이렇게 길어지지는 않을 것 같습니다. 다음 포스팅에서는 API 하위 호환성을 고려해서 어떻게 무중단 배포를 구현했는지 작성해보도록 하겠습니다. 긴 글 읽어주셔서 감사합니다~!!

 

728x90
반응형