# gRPC - gogoproto extension 사용하기

2021. 5. 30. 13:13 개발 이야기/오픈소스

 

안녕하세요. 해커의 개발일기 입니다.

 

앞서 다뤘던 # gRPC 개요 및 proto 파일 정의 에 이어서 조금 심화된 이야기를 하고자 합니다.

gRPC를 사용하면서 마음대로 데이터를 처리하고자 하지 못한 경우가 있었습니다. 이유는 protoc 컴파일을 통해 만들어진 go.pb 파일에 setter가 필요해서 수정해서 사용을 하다가, 지속적으로 필요한 부분을 추가로 코딩해서 사용하고 있었습니다.. 하지만 말씀드렸다시피 이렇게 사용하다보면 어느샌가 메시지 타입이 맞지 않는다는 에러가 발생하게되고 코드로 찾을 수 없는 혼돈의 카오스에 진입하게 되는데요 .. 이런 이슈를 겪고 나니 어떻게 내가 원하는 size, marshaler, getter, setter를 자동으로 만들어 줄 수 있을까? 라는 고민을 하게되었고 오픈소스들을 몇개 열어보니 gogoproto라는 것을 사용해서 아주 쉽게 원하는 것들을 자동으로 만들어서 사용하고 있는 것을 알게되었습니다.

그래서 다른 proto 파일에서 다른 라이브러리를 import해서 사용하는 방법에 대해 알아보도록 하겠습니다.

option go_package = "tsdb";

import "types.proto";
import "google/protobuf/timestamp.proto";
import "google/api/annotations.proto";
import "gogoproto/gogo.proto";


option (gogoproto.sizer_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;

제가 말씀드린 marshaler, getters, sizer를 저 옵션을 통해 간단히 만들거나, 제거할 수 있습니다. 옵션을 넣고 protoc 컴파일을 할 경우 아래 그림과 같이 marshaler가 생성된 것을 볼 수 있습니다. 

먼저 프로젝트 내에서 사용할 경우 import 되는 proto 파일을 프로젝트 내에서 참조할 수 있도록 IDE에서 설정을 해주어야 합니다. goland를 기준으로 설명을 드리면 protocol Buffer 플러그인을 설치한 후 import할 proto 파일들의 경로를 지정해줍니다.

잘 지정이 되었다면 import 해서 사용할 수 있는 옵션들이 잘 불러와 지는지 아래와 같이 확인할 수 있습니다.

저의 경우에는 무식하지만 gogoproto와 googleapis 프로젝트를 모두 다운받아 제 IDL 프로젝트에 추가 시켰습니다. 다른 방법을 찾아보기도 했지만, 스트레스를 덜 받고자 이렇게 진행했습니다(시간 투자 대비 얻는 이익이 적음).

message SpanRef {
  bytes trace_id = 1 [
    (gogoproto.nullable) = false,
    (gogoproto.customtype) = "TraceID",
    (gogoproto.customname) = "TraceID"
  ];
  bytes span_id = 2 [
    (gogoproto.nullable) = false,
    (gogoproto.customtype) = "SpanID",
    (gogoproto.customname) = "SpanID"
  ];
  SpanRefType ref_type = 3;
}

message Process {
  string service_name = 1;
  repeated KeyValue tags = 2 [
    (gogoproto.nullable) = false
  ];
  string cluster_id = 3;
}

gogoproto에는 nullable, customtype, customname 등 다양한 옵션들도 존재합니다. nullable의 경우에는 데이터를 주고 받을 때 null 인 상태로도 주고 받을 수 있도록 해주는 옵션입니다 저 옵션이 없는 경우 null이면 panic이 발생하게 됩니다. nullable 옵션을 넣고 컴파일하게 되면 아래와 같이 omitempty가 없어지게 되는데요 nullable할 수 밖에 없는 데이터를 처리할 경우 해당 옵션을 사용할 수 있습니다.

customtype, customname은 일단 기입된 이름으로 데이터 타입을 만들어주고 당연히 undefined 에러가 발생하게 됩니다. 이렇게 새롭게 직접 정의해서 사용하고 싶으신 경우 custom 옵션을 사용할 수 있습니다.

자 그럼 이렇게 import 한 gogoproto 파일을 이용해 메시지 정의를 다 했으면 컴파일을 하면 되는데요. 컴파일을 했더니 undefined 에러가 계속 발생하게 됩니다. 이유는 protoc 컴파일을 할 때도 gogoproto 등 import 한 proto 파일들을 옵션으로 지정해주어야 하기 때문입니다.

아래의 명령어는 gogoproto와 googleapis를 Import 해서 빌드하는 protoc 명령어입니다.

protoc -I=./github.com/gogo/protobuf -I=./googleapis -I=. --gofast_out=plugins=grpc:output/ test/test.proto

-I 옵션--include_imports 플래그 입니다. 결국 gogoproto를 imports 해서 컴파일하겠다는 의미가 되겠습니다.

이렇게 하면 proto 파일이 너무 길게 정의되지 않고 기능 별로 분리해서 import만 잘해주면 컴파일에 아무 문제가 되지 않습니다. 또한 확장 기능을 통해 유연하게 개발이 가능해지기 때문에 한번 숙지해두면 아주 유용하게 사용할 수 있습니다. 저는 이런 과정을 모르고 컴파일된 pb.go 파일을 수정해서 쓰다가 3일이라는 시간을 날린 기억이 있는데요.. 

저와 같은 스트레스를 받는 개발자를 조금이나마 구출해내고자 포스팅했습니다.

이상으로 proto 파일 정의에 대해서 알아봤습니다.