- 출처: https://developers.google.com/protocol-buffers/docs/proto3
- Message Type 정의
아래는 .proto 파일에 메시지를 정의한 예제이다.
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 맨 첫줄 syntax = "proto3";은 proto3 문법을 사용하겠다는것을 나타낸다. 만약 이 선언이 없다면 proto2를 사용한다고 가정한다.
- 위의 .proto 파일에는 SearchRequest 메시지 안에 3개의 field가 존재한다. 그리고 각 field는 이름과 형을 갖는다. 예제에서는 scala type들만 사용했지만(int32와 string) 열거형이나 다른 메시지 타입도 정의 가능하다.
각 field들에 숫자가 할당되어 있는데 이 field 번호는 이진 형식의 필드를 식별하는데 사용되며 메시지 유형이 사용중이면 변경해서는 안 된다. 1~15 의 field 숫자는 해당 숫자와 형을 포함해서 1 바이트를 차지한다. 16~2047은 2 바이트를 차지한다. 그래서 1~15 값은 매우 자주 발생하는 메시지를 위해 남겨놓는게 좋다. 총 범위는 1 ~ 2^29-1 이며 19000 ~ 19999는 사용할 수 없다.
또한 아래와 같이 Field Rule도 지정할 수 있다.
- singular: 0개 또는 1개를 나타내며 proto3 문법의 default field rule이다.
- repeated: 0개 이상의 반복을 나타내며 순서가 유지된다.
하나의 .proto 파일에는 여러 개의 메시지를 정의할 수 있다. 만약 SearchRequest에 대한 응답 메시지도 정의하고 싶다면 아래와 같이 선언하면 된다.
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
그리고 당연히 comment도 지원한다. //와 /* ... */ 문법을 지원한다.
/* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response. */
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
만약 어떤 field를 삭제하거나 comment 처리하면 나중에 사용자가 해당 field를 재사용할 위험성이 있다. 그때 이전 버전의 같은 .proto 를 로드하면 버그가 발생한다. 이를 방지하기 위해서 삭제된 field 들의 번호를 reserved 키워드로 지정하면 된다.
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
- Scalar Value Types
message 에서 정의한 field type이 string이나 int32와 같이 우리에게 익숙하긴 하지만 어쨌든 .proto 파일을 이용해서 코드를 생성하면 각 언어에 맞는 형으로 생성되기 마련이다. .proto의 메시지 field에서 정의한 형이 내가 사용하는 언어에서 정확히 어떤 형으로 변환되는지 참조하려면 https://developers.google.com/protocol-buffers/docs/proto3#scalar 를 참조하자.
- Default 값들
메시지가 파싱될 때 encode된 메시지가 특정 단일 요소를 포함하고 있지 않으면 기본값으로 세팅된다.
- string은 빈 string 값을 갖는다.
- byte는 빈 byte 값을 갖는다.
- bool은 false 이다.
- numeric은 0이다.
- enum은 처음 정의된 열거형 값이다.
- message 는 설정되지 않으며 정확한 값은 언어적 특성에 종속된다.
- repeated 는 비어 있다.
- 열거형
message내의 어떤 field가 정의된 값중 하나를 갖도록 제한하고 싶을 때에는 enum을 사용한다.
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
위의 예제에서 Corpus enum의 첫번째 상수는 0으로 설정되어 있다. 모든 enum 정의는 반드시 처음 요소로 0에 맵핑되는 상수를 가져야 하는데 그 이유는 아래와 같다.
- 첫번째 요소가 0값이어야 numeric 기본값으로 0을 사용할 수 있다.
- 첫번째 enum 값이 언제나 기본값인 proto2 와의 의미 호환성을 위해 0값이 첫번째 요소가 되어야 한다.
- 다른 Message 참조
프로그래밍 언어에서 하나의 자료형에서 다른 자료형을 참조할 수 있듯이 proto에서도 다른 message를 field로 참조할 수 있다. 만약 SearchResponse message에서 Result message를 참조하고 싶다면 아래와 같이 하면 된다.
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
- 중첩 Type
message type 안에 다른 message type을 중첩으로 사용할 수 있다.
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
만약 외부의 message에서 Result를 재사용하고 싶다면 아래와 같이 사용해야 한다.
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
- Message Type 수정
message type을 갱신할 때 기존의 message 타입의 동작을 보장하면서 수정해야 하는 경우가 있다. 그럴때에는 아래와 같은 규칙을 명심하면서 작업하도록 하자.
- 기존에 존재하던 field number를 변경하지 말것
- 새로운 field를 추가한 후 .proto에서 생성된 코드를 통해 message를 파싱할 때 기존의 message도 파싱된다.
- field number가 수정된 message 형에서 다시 사용되지 않으면 field를 삭제할 수 있다. 대신 field 명을 OBSOLETE_와 같은 접두사를 붙여줘서 이름을 변경해주거나 해당 field number를 reserved로 만들어주도록 하자.
- int32, uint32, int64, uint64, bool은 모두 호환된다.
- sint32, sint64는 호환되지만 다른 integer 형들과는 호환되지 않는다.
- string과 bytes는 호환된다 단, bytes가 유효한 UTF-8이어야 한다.
- Embedded message들은 bytes와 호환된다.
- 단일 값을 새로운 oneof의 멤버로 변환하는것은 안전하며 binary 호환된다. multiple field를 새로운 oneof로 옮기는 것은 코드가 한번에 둘 이상을 설정하지 않을 경우 안전하다. any field를 이미 존재하는 oneof로 옮기는것은 안전하지 않다.
- 그 외의 추가 정보는 https://developers.google.com/protocol-buffers/docs/proto3#updating 여기서 참조 바람.
- Any
Any message 형은 .proto 에서 별도 정의 없이 embedded 형으로 message를 사용할 수 있도록 해준다. Any는 bytes로 직렬화된 임의의 메시지를 포함하며 해당 메시지내에는 전역적으로 고유하게 식별하는 URL 정보도 포함되어 있다. Any 형을 사용하기 위해서는 google/protobuf/any.proto 를 import 해야 한다.
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
message 형에 주어지는 기본 URL은 https://type.googleapis.com/_packagename_._messagename_ 이다.
- Oneof
만약 message에 여러 개의 field가 있고 그 중에서 최대 1개의 field만 사용되어야 한다면 oneof를 이용해서 이 기능을 구현하여 메모리를 절약할 수 있다.
Oneof field는 oneof 공유 메모리의 모든 field를 제외하고 일반 field와 같으며 최대 하나의 field를 동시에 설정할 수 있다. oneof 의 어떤 member라도 세팅되면 나머지 member들을 제외한다. oneof의 어떤 값이 세팅되었는지를 case()나 WhichOneof() 메소드를 사용해서 확인할 수 있으며 선택하는 언어에 따라 다르다.
.proto 에서 oneof를 정의하기 위해서는 아래와 같이 사용하면 된다.
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
oneof 정의에 oneof field를 더해주면 된다. map과 repeated field를 제외한 나머지 field는 모두 사용할 수 있다.
생성된 코드에서 oneof field는 일반적인 field 처럼 getter/setter를 동일하게 갖는다. 또한 어떤 값이 사용되었는지 확인할 수 있는 특별한 method도 생성된다.
oneof는 몇 가지 특징을 갖는데, 아래 특징을 숙지한 후 사용하도록 하자.
- oneof field를 설정하면 oneof의 나머지 member들은 자동으로 clear 된다. 만약 oneof field들을 여러 번 설정하면 맨 마지막 field만 값을 갖는다.
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
- 파서가 같은 oneof의 member를 여러 번 만나면 마지막 member만 파싱한다.
- oneof는 repeated가 될 수 없다.
하위 호환성 issue와 관련해서 oneof field를 추가하거나 삭제할 때에는 조심해야 한다. oneof 확인 값이 None/NON_SET을 반환했다면 oneof가 셋팅되지 않았거나 oneof의 다른 version의 field가 설정된것이다.
field를 oneof 안으로 혹은 밖으로 옮기면 message가 직렬화되고 파싱된 후에 일부 정보를 잃을 수 있다. 하지만 하나의 field를 oneof의 새로운 field로 옮기는것은 안전하며 오직 하나만 설정된다는 보장이 있으면 여러 개의 field를 옮겨도 된다.
- Packages
package 를 사용하면 protocol message type들 간의 name 충돌을 방지할 수 있다.
package foo.bar;
message Open { ... }
위의 예제에서 Open을 message의 field로 사용하고 싶다면 아래와 같이 사용하면 된다.
message Foo {
...
foo.bar.Open open = 1;
...
}
package는 지정자는 언어에 따라 생성되는 코드에 영향을 줄 수 있다. 여러 언어가 설명되어 있지만 나는 go를 사용하고 있기 때문에 go 부분만 기술한다. Go에서는 .proto 파일에서 명시적으로 option go_package 를 설정하지 않으면 Go package name으로 package를 사용한다.
- 그 외
Project를 진행하다가 proto3에 대해 알아야 할 필요가 있고 관련된 부분만 정리했지만 기술하지 않은 다른 특징들도 많다. UnKnown Fields, Maps, Service, JSON Mapping, Options 등도 있으니 각자 필요한 부분 참조하자.
'Framework and Tool > gRPC and Protobuf' 카테고리의 다른 글
Protocol buffer - Convention (0) | 2022.04.29 |
---|---|
Protocol buffer (0) | 2022.03.13 |
gRPC with protobuf (0) | 2022.03.13 |
RPC(Remote procedure call) (0) | 2022.03.13 |
댓글