최근에, 강남언니 기술블로그에 기술된 글을 재미있게 본 적이 있습니다.
강남언니 블로그에서는, 프론트엔드와 백엔드 간의 api를 protocol buffer로 정의 하면서 아래와 같은 이점을 얻을 수 있었다고 합니다.
1. API 문서를 유지보수하지 않아도 되게 되었습니다.
기존에는 api가 변경되면, api 문서도 업데이트 해줘야했는데, protocol buffer로 관리하면서, api 변경이 곧 문서의 변경으로 바로 이어졌습니다.
2. 단일 진실 공급원(Single Source Of Truth) 환경이 구축됩니다.
프론트엔드와 백엔드가 각각 똑같은 IDL(protocol buffer) 를 공유하면서, 휴먼에러가 발생할 확률이 줄어듭니다.
저 또한 이런 이점에 공감해, 새롭게 진행하고 있는 프로젝트에 Protocol buffer로 API 문서를 작성해보고자 했습니다.
이 아티클에 기술된 모든 코드는 아래 레포지토리에서 볼 수 있습니다.
https://github.com/rooftop-MSA/api-spec
ProtocolBuffer API Template 만들기
구글에서, Http 관련 proto 파일을 제공해주지만 제가 사용하려는 목적과 구현이 달라 실제로 사용하기에는 부족한 부분이 많았습니다. 그래서, 직접 구현하기로 결심했고 아래와 같은 템플릿을 만들게 되었습니다.
- 13번 oneof pattern은 pattern중 하나를 선택하도록 하고, HTTP 프로토콜의 메소드를 뜻합니다.
- 21번 params는 HTTP의 request parameter를 뜻합니다.
- 23번 headers는 HTTP의 Header를 뜻합니다.
- 25번 internal_api는 내부적으로 사용할 api라면 true로 표시하는것으로, 코드상의 동작은 없지만, 문서상의 표현을 위해 구현해주었습니다.
- 27번 responses는 api의 응답 상태코드와 이유를 명시하는 필드입니다. StatusCode라는 enum proto파일을 만들어서 관리하고 있습니다. StatusCode enum파일에 대한 자세한 내용은 https://github.com/rooftop-MSA/api-spec/blob/main/core/status_code.proto 파일을 참고해주세요.
-30번 처럼 extend ServiceOptions을 넣어주면, Service 블록에서 해당 Http 메시지를 option으로 정의할 수 있게 됩니다.
아래는 사용하는 예시입니다.
ProtocolBuffer로 API 작성
이제, 위에서 만든 템플릿을 활용해서 실제로 API을 작성해보겠습니다.
간단하게 유저의 아이디, 비밀번호, 이름을 입력받아 유저를 생성하는 API를 정의했습니다
요청(혹은 응답)은 message를 정의함으로 구현할 수 있습니다.
9번 라인을 보면, UserCreateReq가 정의되어있고, 이 메시지가 HTTP body에 담겨서 전달됩니다.
15번 라인 부터는 HTTP spec을 명시합니다.
위 섹션에서 정의한 http.proto 파일을 통해서 어떠한 메소드를 사용할지, header에는 어떠한 값이 같이 전달되어야 하는지 명시할 수 있습니다.
위 코드 같은 경우는 post /v1/users로 전달되며 header로 application/x-protobuf를 전달해야 함을 알 수 있습니다.
또한, responses 를 명시해서 각 요청마다 예상되는 서버의 Http response status code 를 명시 해주었습니다.
36번은 라인은 body에 어떠한 값을 넣어야하는지, 어떠한 값이 응답되는지 표현합니다. UserCreateReq를 전달했을때, Empty body가 응답됨을 알 수 있습니다.
Gradle 프로젝트에서 사용하기
gradle 프로젝트에서 사용하기 위해서 다음 코드와 같이 gradle 설정을 해주면 됩니다.
저는 kotlin + gradle 조합을 함께 사용하고 있습니다.
protobuf 블록에 artifact 에 어떤 버전의 protobuf 컴파일러를 사용할지 명시해주어야 하며,
generateProtoTasks에 kotlin버전으로도 만들기 위해서, kotlin { } 을 넣어주었습니다.
kotlin에서만 사용할것이므로, dependence블록에 java를 넣지 않는 실수를 범할 수 있는데, kotlin 의존성은 기존의 java 의존성으로 만들어진 파일을 kotlin 에서 사용하기 쉽게 만들어주는것에 불과합니다. 즉, java와 kotlin 둘다 명시해주어야 합니다.
또한, 저처럼, protocol buffer파일을 기본위치 (resource하위로 기억하는데 정확하지는 않습니다.) 가 아닌 다른 위치 (저의 경우 api-spec) 에서 관리하고 싶다면, dependencies 블록에 api-spec을 명시해 주어야 합니다.
protocol buffer 컴파일은 아래 명령어를 통해서 할 수 있습니다. (test나 build시에도 자동으로 생성됩니다.)
./gradlew generateProto
코틀린에서는 아래와 같은 방식으로 사용할 수 있습니다. (귀찮게 Builder를 사용하지 않아도 됩니다.)
장점과 단점
실제로 적용하면서 느낌 장점과 단점을 적어보도록 하겠습니다.
단점을 보완할 수 있을거 같거나, 장점이 더 크다고 생각되신다면 적용을 고려해봐도 좋을거 같아요.
(저 또한, 이 글을 작성하는 시점에도 계속해서 디벨롭 하고 있습니다.)
장점
1. API문서 또한, 코드(IDL) 이므로, CI(Continuous Integration)을 적용할 수 있었고, 일관된 API 문서 컨벤션을 유지할 수 있었습니다.
Protocol buffer 컨벤션에 맞지 않는 부분을 찾아 줍니다.
2. 깃 서브모듈로 관리하면서, 코드리뷰를 진행할 수 있었습니다. 예를 들어, 프론트와 백엔드가 함께 있는 레포지토리에서 코드리뷰를 받아야만 API를 반영할 수 있는 구조를 강제할 수 있습니다.
3. 단일 진실 공급원(SSOT) 환경이 구축되는데, SSOT가 주는 이점이, MSA 환경일때 더욱 극대화 된다는 느낌을 받았습니다.
서로 다른 도메인끼리 통신할때, 각 서버별로 요청 응답 모델을 따로 구현하지 않아도 되는 편리함이 있었습니다.
통신이 더 자주 발생하는 MSA구조에서는 요청 응답 모델을 따로 구현하지 않아도 되는 편리함이 러닝커브를 넘어선다고 생각합니다.
4. ProtocolBuffer는 json 방식에 비해서 사이즈가 적으며, 인코딩, 디코딩이 빠릅니다.
protocol buffer는 json field의 key를 number로 치환해서 인코딩 하기 때문에, 용량의 이점을 얻을 수 있습니다.
단점
1. 디버깅하기 힘듭니다.
Protocol buffer 통신은 JSON과 같이 읽기 쉽지 않습니다.
2. 기존의 JSON방식에 비해서, 러닝 커브가 높습니다.
'끄적끄적' 카테고리의 다른 글
[끄적끄적] 결제 중복 롤백 방지하기 (3) | 2024.03.31 |
---|---|
[끄적끄적] @Transactional 안에서 retry 사용을 주의하세요 (0) | 2024.02.21 |
[끄적끄적] E2E(API) 테스트 자동화 도입기 (0) | 2023.10.18 |
[Sonarqube] Sonarqube 설치 + PR decoration 하기 (0) | 2023.05.07 |
[끄적끄적] Rust로 PS 후기 (0) | 2023.04.17 |