프로그래밍/JAVA Spring

버저닝 ...

hectick 2023. 10. 15. 23:51

 

안드로이드와 협업을 할 때 버저닝은 필수적이다.

 

왜냐하면 죽어도 앱 업데이트를 안하는 사람들(대표적으로 나)가 있기 때문

올해 중순까지만 해도 카카오톡 업데이트를 안해서 보이스톡 기능을 못썼다.

지금 쿠팡앱 켜도 맨날 업데이트 하라고 하는데 이악물고 무시중...

핸드폰 저장공간이 부족한 탓이다.

하지만 죽어도 게임은 못지움.

아무리 권태기가 오더라도 언젠간 다시 돌아가기 때문

 

어쨌든

 

안드로이드 앱의 버전은 1.1.2 이런 형식인데

(Major).(Minor).(Patch) 이렇게 구성된다.

우리 이돈이면 팀은 대충

Api가 바뀌어서 버전 호환이 안되면 Major 버전이 올라가고,

기존의 Api는 바뀌지 않고 새로운 Api가 추가되거나 비즈니스 로직이 변경되는 정도에서 끝나면 Minor 버전이 올라가고,

버그 수정등은 Patch 버전을 올린다.

 

이돈이면 팀이 뭔지 궁금할 수 있을 것 같은데

내가 우아한테크코스에서 백엔드로 참여하고 있는 팀이다.

레포지토리좀 홍보하겠다.

https://github.com/woowacourse-teams/2023-edonymyeon

 

GitHub - woowacourse-teams/2023-edonymyeon: 사용자들에게 소비 절제와 합리적인 소비를 도모하며 재미와

사용자들에게 소비 절제와 합리적인 소비를 도모하며 재미와 유용한 정보를 제공하는 서비스. Contribute to woowacourse-teams/2023-edonymyeon development by creating an account on GitHub.

github.com

플레이스토어도 홍보하겠다. 다운좀 받아주세요. plz...

https://play.google.com/store/apps/details?id=app.edonymyeon 

 

이돈이면 - Google Play 앱

사용자들에게 소비 절제와 합리적인 소비를 도모하며 재미와 유용한 정보를 제공하는 서비스입니다.

play.google.com

 

 

다시 어쨌든

 

우리팀 백엔드는 안드로이드의 모든 버전을 최대한 지원하기로 했다.

그렇기 때문에 API가 바뀌는 Major 버전의 업데이트가 일어나도, 예를들면 1.0.0 회원정보수정과 2.0.0 버전의 회원정보수정 API를 모두 지원할 수 있어야 했다.

그래서 커스텀 헤더를 이용한 버저닝을 도입하기로 했음

uri로 버저닝하는거, path variable로 하는거 등 많았는데

path variable은 버전말고 비즈니스와 연관된 다른 변수들이 들어올 수도 있기 때문에 사용하는 목적을 분리하기위해 제외했고

안드로이드측에서 api보단 헤더로 버저닝하는게 쉽다고 해서 그렇게 정했다.

 

안드로이드가 보내는 X-API-VERSION 헤더를 받는 가장 쉬운 방법은 아래처럼 인자를 살짝 추가해주는 것이다.

    @GetMapping(value = "/posts", headers="X-API-VERSION=1")

 

그런데 이렇게 하면,

예를들어 회원 정보 수정 기능 api가 변경되서 major 버전의 업데이트가 1 -> 2로 일어났다고 했을 때,

다른 게시글 작성, 게시글 수정, 댓글 작성 등등 다른 모든 기능들에 대해서 major 버전 1과 버전2를 지원하기 위해 컨트롤러에 여러개의 메서드를 더 추가해주어야 한다.

우리는 major 버전의 업데이트가 일어나도 변경되지 않은 api에 대해서는 컨트롤러에 하나의 메서드만을 사용하길 원했고

커스텀 어노테이션을 이용한 버저닝이란 것을 찾아내었다.

https://sg-choi.tistory.com/279

 

[Spring] Controller

Controller 기본 get @GetMapping("/api/get") public Map get( @RequestHeader(required = false) Map headers, @RequestParam(required = false) Map params ) { System.out.println(headers); System.out.println(params); return params; } post @PostMapping("/api/pos

sg-choi.tistory.com

 

그래서 어노테이션을 만들어주었다.

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ApiVersion {
        int[] value();
    }

메서드 위에도 붙이고, 메서드에 일일히 붙이기 귀찮다면 클래스에 붙여줄 수 있겠지? 라고 생각하고 만들었다.

버전을 정수로 저장하는 이유는

1.1.0, 1.0.0, 1.3.2 등 Major 버전만 같다면 같은 버전이라고 판단하기 위함이였다.

왜냐하면 세부 버전도 기록해놓으면 컨트롤러가 이렇게 될것이 뻔했기 때문

나는 편한길로 가고 싶었다.

 

마침 이때가 우테코에서 어노테이션기반 MVC 만들기 미션을 하고 있던 때라

@RequestMapping 어노테이션이 달린 메서드들을 찾아내서 매핑 정보를 만드는 RequestMappingHandlerMapping을 상속해서 기능을 추가해주는게 정말 흥미로워 보였다.

그런데 위에 블로그를 참고해서 만들었는데 오잉.. 클래스단에 @ApiVersion 어노테이션을 달면 버저닝이 잘 안되네??

원래 X-API-VERSION 헤더가 존재해야 정상 응답을 받고, 버전 헤더가 없거나, 버전이 일치하지 않으면 404응답을 받아야 한다.

그런데 우리는 버전이 있든 없든 응답이 잘왔다.

왜 안되는진 모르겠지만 이때 워낙 바빴기 때문에 메서드단에 어노테이션을 모조리 달아주고 나중에 방법을 찾아보자 하고 덮어놨음.

 

그러다가 어제 아침 일찍 눈이 떠져서 차례차례 디버깅을 해보았다.

일단 클래스단에 ApiVersion이 달려있을때 버전에 대한 매핑 조건을 포함한 RequestCondition은 생성이 되긴 한다.

 

그런데 클래스에는 @RequestMapping이라는 어노테이션이 붙어있질 않으니

기껏 만든 Condition 말고 null이라는 매핑 정보가 반환되고 있었다.

 

아래 코드를 보면, 이제 위에서 반환한 null 때문에...

걍 @RequestMapping이 아주 잘~ 붙어있는 메서드에 달려있는 @ApiVersion으로 생성된 매핑 정보만 최종적으로 사용되게 되는 것이었다. 

그래서 클래스단에 아무리 달아봤자 매핑정보가 생성되도 사용되지 못하고, 소용이 없던 것..

 

흠!! 그러면 @APiVersion을 쓰려면 클래스단에도 @RequestMapping을 달면 되겠군!! 하고 달아보니 매핑정보가 잘 사용된다.

버저닝 헤더가 없으면 404응답이 오는것도 확인했다.

사실 RequestMappingHandlerMapping 클래스가 이미 클래스명으로 말해주고 있었는데

내가 눈치를 못채고 있던 것이다..

대놓고 나는 @RequestMapping에 대한 클래스야하고~~ 말하고 있지않나..

 

그렇지만 클래스단에 @ApiVersion 어노테이션을 붙이기위해 @RequestMapping을 붙여주는것도 구리다.

그냥 메서드단에만 달도록 해야겠다.

 

이상 포스팅 끝