kafka와 protocol buffer를 사용하는 프로젝트를 진행했으며, 시간이 지남에 따라 당시에 익혔던 지식들을 잊어먹고 있어서 리마인드 목적으로 정리해보았습니다.

프로토콜 버퍼는 직렬화 데이타 구조 (Serialized Data Structure)이다

프로토콜 버퍼는 구글에서 개발한 오픈소스 직렬화 데이타 구조 입니다.

파일로 저장하거나 네트워크상에서 데이터를 전달할 때 바이트 스트림 형태로 변환하는데 이를 직렬화라고 합니다.

프로토콜 버퍼는 이 직렬화때 생성되는 바이트의 사이즈가 상대적으로 작고, 직렬화 속도가 빠릅니다.

proto file

프로토콜 버퍼는 여러 언어를 지원하는데, 이때 언어에 종속적이지 않도록 하기 위하여 proto 라는 형식의 파일을 사용합니다.

확장자는 .proto 이며

아래는 proto 파일의 예시입니다.

syntax = "proto3";

package myapp;

message User {
  int32 id = 1;
  string name = 2;
  int32 age = 3;
  repeated string hobbies = 4;
  repeated Address addresses = 5;

  message Address {
    string street = 1;
    string city = 2;
    string state = 3;
    string country = 4;
    string postal_code = 5;
  }
}

여기서 유의해야하는 부분이 있습니다. syntax = "proto3"; 부분인데

프로토콜 버퍼는 버전이 존재합니다. 이 버전에 따라서 proto 의 형식이 달라지는데

어떤 버전을 사용할지에 따라 양식을 맞춰야합니다.

크게 2버전과 3번으로 나뉘며 저의 경우 3 버전을 사용했습니다.

사용

proto 파일을 정의했다면 이제 컴파일러를 이용하여 사용할 언어로 컴파일해야합니다.

프로토콜 버퍼 컴파일러는 공식 페이지 또는 깃허브에서 다운받을 수 있으며,

사용할 언어를 정한 후 아래와 같이 컴파일을 진행하면 해당 언어로 변환된 파일을 얻을 수 있습니다.

저는 java에서 사용하였기에 java를 예시로 들어보겠습니다.

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

이런 식으로 사용할 수 있는데 저 같은 경우는 gradle 이라는 빌드 도구를 사용하였고 해당 빌드 도구에서 사용할 수 있는 protobuf-gradle-plugin 라는 구글이 개발한 플러그인을 사용하여 직접 컴파일을 돌리지 않고 프로젝트 빌드 시 자동으로 컴파일이 이뤄지도록 하였습니다.

저와 같이 gradle 을 사용하시는 분은 해당 플러그인을 사용하시는 것도 좋은 방법입니다.

결과물로 --java_out 에 지정된 경로에 파일이 생성되는데 저의 경우 User.java 파일이 생성됩니다.

만약 C++로 하면 .cpp 파일이 생길거고 python 으로하면 .py 파일이 생길겁니다.

잠깐! : 제가 프로토콜 버퍼를 사용한 이유가 바로 여기있습니다!. 통신을 하면서 주고 받을 데이터 구조가 정해져있는데. 주고 받는 서비스가 각자 다른 언어로 개발될 경우 언어별로 개발자들이 직접 구조에 맞는 데이터 구조를 코딩해야합니다. 데이터 구조가 바뀌거나 새로운 데이터 구조가 추가될 때마다 이 작업이 이뤄져야하는데 여간 번거롭습니다. 이를 개선하고자 프로토콜 버퍼를 도입하여 proto 파일만 공유하면서 데이터 구조 변화에도 작업량을 최소화 시킬 수 있다는 이득을 보았습니다.

생성된 User.java 파일의 본문은 생각보다 긴 편일텐대

해당 클래스에 대한 직렬화, 역직렬화 를 도와주는 편의 함수들이 자동으로 다 생성되어있습니다.

User.java를 사용하는 java 코드를 보겠습니다

아래의 경우 직렬화 예시입니다

// 객체를 직렬화하여 파일에 저장
public static void main(String[] args) throws Exception {
    User user = User.newBuilder()
        .set=Id(123)
        .setName("wusub")
        //~~~
        .build();

    FileOutputStream output = new FileOutputStream(args[0]);
    user.writeTo(output);
    output.close();
}

그 다음은 역질렬화 예시입니다.

// 파일에 저장되어있던 데이터를 읽어서 역직렬화하여 객체로 변환
public static void main(String[] args) throws Exception {
    User user = User.parseFrom(new FileInputStream(args[0]); 
}

참 간단하죠? 저의 경우 kafka에서 topic에 데이터를 쌓을때 byte 형태로 저장하기 때문에 직렬화, 역직렬화를 꼭 거쳐야합니다. 그렇기 때문에 프로토콜 버퍼를 이용한 직렬화 데이터를 주고 받기에 딱 안성 맞춤이었습니다.

더 자세한 정보들은 공식 도큐먼트가 상당히 잘 되어있기에 참고해주시면 감사하겠습니다.

공식 문서 : https://protobuf.dev/