Protocol Buffers

w00j00ng351·2022년 12월 29일
0

grpc

목록 보기
1/1

Protocol Buffers

1. 개념

1.1. Protocol Buffers 란?

  • 구글이 개발한 직렬화 데이터 구조
  • 구조화된 데이터(structured data)를 직렬화(serialize)하는 방법
  • Protocol Buffers 는 구조화된 데이터를 이진수로 직렬화 함
  • 직렬화 데이터 구조의 예: xml, json

1.2. 활용 방법

  • proto 문법에 맞게 proto 파일 작성
  • protoc 를 통해 proto 파일을 컴파일
  • 컴파일의 결과로 프로그래밍 언어에서 활용할 수 있는 코드가 생성됨
  • 생성된 코드에는 Protocol Buffers 형식으로 직렬화, 역직렬화할 수 있는 함수가 있음

1.3. proto3

  • 메시지 형식
syntax = "proto3";  // 파일의 첫 번째 줄은 `proto3` 구문을 사용하도록 지정

message Product { // 메시지에 포함할 각 데이터에 하나씩, 필드 (이름/값 쌍)을 지정
	int32 id = 1;
    string name = 2;
}
  • 자료형

    • double, float, int32, int64, uint32, uint64
    • sint32, sint64 (s: singed, 음수 연산에 더 효율적)
    • fixed32, fixed64 (고정길이 인코딩)
    • sfixed32, sfixed64
    • bool
    • string
    • bytes
    • enums
    • 기본 값은 언어에 의존적임
  • enums 활용 예

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  Corpus corpus = 4;
}
  • 중첩 자료형

    • message type 의 필드 자료형으로 다른 message type 을 지정할 수 있음

    • 중첩 자료형 예 1

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}
  • 중첩 자료형 예 2
message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

1.4. 인코딩

  • (주의) 1.4. 인코딩 방법에 관한 내용 중 일부는 필자의 개인 분석에 근거한 부분이 있음. 내용이 부정확할 수 있음
  • 첫 5개 bit에 필드 key (순서) 입력

  • 그 뒤 3개 bit 에 필드 자료형에 맞는 와이어 유형 입력

  • (공식문서에는 첫번째 byte 에서 마지막 3개 bit 에 와이어 유형 값을 입력하고, 나머지 bit 에 필드 key 를 입력한다고 작성됨)

  • 와이어 유형

    • 0 (VARIANT): int32, int64, uint32, uint64, sint32, sint64, boolean, enum
    • 1 (I64): fixed64, sfixed64, double
    • 2 (LEN): string, bytes, embedded messages, packed repeated fields
    • 5 (I32): fixed32, sfixed32, float
  • 그 다음 bytes 에 입력되는 값은 와이어 유형에 따라 다름 (아래 내용은 필자의 개인 분석에 근거함, 내용 불확실)

    • 와이어 유형 값이 0인 경우, 그 다음 1개 byte 에 값을 입력
    • 와이어 유형 값이 1인 경우, 그 다음 8개 bytes 에 값을 입력
    • 와이어 유형 값이 2인 경우, 그 다음 1개 byte 읽을 byte 수를 입력, 그 수 만큼의 byte 에 값을 입력
    • 와이어 유형 값이 5인 경우, 그 다음 4개 bytes 에 값을 입력
  • 더 살펴볼 내용

2. protoc 설치

  • package manager 를 통한 설치
$ sudo apt update -y && sudo apt install -y protobuf-compiler
$ protoc --version
libprotoc 3.12.4

3. proto 파일 작성

  • 프로젝트 폴더 생성
$ rm -rf ~/myproj && mkdir ~/myproj && cd ~/myproj
  • protobuf 파일 작성
$ vi product.proto
syntax = "proto3";

message Product {
    int32 id = 1;
    string name = 2;
    bool display = 3;
}

4. code 생성 및 byte 변환

4.1. Python Protobuf

  • 폴더 및 가상환경 구성
$ mkdir ~/myproj/mypy && cd ~/myproj/mypy
$ python3 -m venv venv # 가상환경 생성
$ source venv/bin/activate # 가상환경 접속
(venv) $ pip install --upgrade pip # pip 업데이트
(venv) $
  • grpcio, grpcio-tools 패키지 설치
(venv) $ pip install grpcio grpcio-tools
  • protobuf 로 생성되는 코드를 저장할 디렉터리 생성
(venv) $ mkdir ~/myproj/mypy/product
  • 코드 생성
(venv) $ python -m grpc_tools.protoc \
    -I=$HOME/myproj \
    --python_out=$HOME/myproj/mypy/product \
     --pyi_out=$HOME/myproj/mypy/product \
     --grpc_python_out=$HOME/myproj/mypy/product \
    product.proto
  • ~/myproj/mypy/product 디렉터리에 grpc 관련 코드 파일이 생성됨
(venv) $ ls ~/myproj/mypy/product
product_pb2.py  product_pb2.pyi  product_pb2_grpc.py
  • 생성된 코드로 protobuf 바이트 생성
    • 아래 코드 작성
(venv) $ vi ~/myproj/mypy/main.py
from product.product_pb2 import Product


# 테스트용 object 생성
p1: Product = Product(
    id=1,
    name="가방",
    display=True,
)

# object 를 bytes 로 직렬화
protobuf_bytes: bytes = p1.SerializePartialToString()

# 직렬화한 bytes 의 길이와 내용 확인
print("byte 길이:", len(protobuf_bytes))
print("byte 내용:", protobuf_bytes)

# 직렬화한 bytes 의 내용을 파일에 저장
with open("../bytes_from_python", "wb") as f:
    f.write(protobuf_bytes)

# 파일에서 bytes 의 내용을 읽어옴
protobuf_bytes_read_from_file: bytes = bytes()
with open("../bytes_from_python", "rb") as f:
    protobuf_bytes_read_from_file = f.read()

# 읽어온 bytes 의 내용을 새 object 에 할당
p2: Product = Product()
p2.ParseFromString(protobuf_bytes_read_from_file)

# 새로 할당한 object 의 내용 확인
print(p2)
  • 출력 결과
(venv) $ python ~/myproj/mypy/main.py
byte 길이: 12
byte 내용: b'\x08\x01\x12\x06\xea\xb0\x80\xeb\xb0\xa9\x18\x01'
id: 1
name: "가방"
display: true
  • 생성된 파일의 bit 내용 확인
$ xxd -b ~/myproj/bytes_from_python
00000000: 00001000 00000001 00010010 00000110 11101010 10110000  ......
00000006: 10000000 11101011 10110000 10101001 00011000 00000001  ......

4.2. Nodejs Protobuf (미결, 실패)

$ mkdir ~/myproj/myjs && cd ~/myproj/myjs
$ npm init -d # npm 환경 구성 (~/myproj/myjs 디렉터리에서 수행)
  • grpc-tools, google-protobuf, @grpc/grpc-js 패키지 설치
$ npm install grpc-tools google-protobuf @grpc/grpc-js
  • 코드 생성
$ $(npm bin)/grpc_tools_node_protoc \
    --proto_path=$HOME/myproj \
    --js_out=import_style=commonjs,binary:$HOME/myproj/myjs/product \
    --grpc_out=grpc_js:$HOME/myproj/myjs/product \
    product.proto
  • ~/myproj/myjs/product 디렉터리에 grpc 관련 코드 파일이 생성됨
$ ls ~/myproj/myjs/product
product_grpc_pb.js  product_pb.js

4.3. Go Protobuf

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
  • 환경변수 설정
$ export PATH="$PATH:$(go env GOPATH)/bin"
  • 폴더 생성 및 go 모듈 설정
$ mkdir ~/myproj/mygo && cd ~/myproj/mygo
$ go mod init mygo
  • protobuf 로 생성되는 코드를 저장할 디렉터리 생성
(venv) $ mkdir ~/myproj/mygo/product
  • proto 파일 수정

    • syntax 문장 다음에 option go_package="product/pb"; 문장 추가
syntax = "proto3";

option go_package="product/pb";

message Product {
    int32 id = 1;
    string name = 2;
    bool display = 3;
}
  • 코드 생성
$ protoc -I $HOME/myproj \
    --go_out=. \
    --go-grpc_out=. \
    product.proto
  • ~/myproj/mygo/product/pb 디렉터리에 grpc 관련 코드 파일이 생성됨
$ ls ~/myproj/mygo/product/pb
product.pb.go
  • 생성된 코드로 protobuf 바이트 생성
    • 모듈 종속성 최신화
$ go mod tidy
  • 아래 코드 작성
$ vi ~/myproj/mygo/main.go
package main

import (
    "log"
    "mygo/product/pb"
    "os"

    "google.golang.org/protobuf/proto"
)

func main() {
    log.SetFlags(0)

    // 테스트용 object 생성
    p1 := &pb.Product{
        Id:      1,
        Name:    "가방",
        Display: true,
    }

    // object 를 bytes 로 직렬화
    pMessage := p1.ProtoReflect().Interface()
    protoBytes, err := proto.Marshal(pMessage)
    if err != nil {
        log.Fatal(err.Error())
    }

    // 직렬화한 bytes 의 길이와 내용 확인
    log.Println("byte 길이:", len(protoBytes))
    log.Println("byte 내용:", protoBytes)

    // 직렬화한 bytes 의 내용을 파일에 저장
    err = os.WriteFile("../bytes_from_go", protoBytes, os.ModePerm)
    if err != nil {
        log.Fatal(err.Error())
    }

    // 파일에서 bytes 의 내용을 읽어옴
    protobufBytesReadFromFile, err := os.ReadFile("../bytes_from_go")
    if err != nil {
        log.Fatal(err.Error())
    }

    // 읽어온 bytes 의 내용을 새 object 에 할당
    p2 := new(pb.Product)
    if err := proto.Unmarshal(protobufBytesReadFromFile, p2); err != nil {
        log.Fatal(err.Error())
    }

    // 새로 할당한 object 의 내용 확인
    log.Println(p2)
}
  • 출력 결과
byte 길이: 12
byte 내용: [8 1 18 6 234 176 128 235 176 169 24 1]
id:1 name:"가방" display:true
  • 생성된 파일의 bit 내용 확인
$ xxd -b ~/myproj/bytes_from_go
00000000: 00001000 00000001 00010010 00000110 11101010 10110000  ......
00000006: 10000000 11101011 10110000 10101001 00011000 00000001  ......

4.4. 서로 다른 언어가 생성한 바이트 코드 비교

  • python 에서 go 가 생성한 바이트 코드를 읽어와 서로 비교
(venv) $ vi ~/myproj/mypy/main.py
from product.product_pb2 import Product


# 테스트용 object 생성
p1: Product = Product(
    id=1,
    name="가방",
    display=True,
)

# object 를 bytes 로 직렬화
protobuf_bytes: bytes = p1.SerializePartialToString()

# 직렬화한 bytes 의 길이와 내용 확인
print("byte 길이:", len(protobuf_bytes))
print("byte 내용:", protobuf_bytes)

# 직렬화한 bytes 의 내용을 파일에 저장
with open("../bytes_from_python", "wb") as f:
    f.write(protobuf_bytes)

# 파일에서 bytes 의 내용을 읽어옴
protobuf_bytes_read_from_file: bytes = bytes()
with open("../bytes_from_go", "rb") as f:
    protobuf_bytes_read_from_file = f.read()

# 읽어온 bytes 의 내용을 새 object 에 할당
p2: Product = Product()
p2.ParseFromString(protobuf_bytes_read_from_file)

# 새로 할당한 object 의 내용 확인
print(p2)

# python 이 생성한 바이트와 go 가 생성한 바이트 비교 
print("protobuf_bytes == protobuf_bytes_read_from_file:", protobuf_bytes == protobuf_bytes_read_from_file)
$ python ~/myproj/mypy/main.py
byte 길이: 12
byte 내용: b'\x08\x01\x12\x06\xea\xb0\x80\xeb\xb0\xa9\x18\x01'
id: 1
name: "가방"
display: true

protobuf_bytes == protobuf_bytes_read_from_file: True
  • go 에서 python 이 생성한 바이트 코드를 읽어와 서로 비교
package main

import (
    "log"
    "mygo/product/pb"
    "os"

    "google.golang.org/protobuf/proto"
)

func main() {
    log.SetFlags(0)

    // 테스트용 object 생성
    p1 := &pb.Product{
        Id:      1,
        Name:    "가방",
        Display: true,
    }

    // object 를 bytes 로 직렬화
    pMessage := p1.ProtoReflect().Interface()
    protoBytes, err := proto.Marshal(pMessage)
    if err != nil {
        log.Fatal(err.Error())
    }

    // 직렬화한 bytes 의 길이와 내용 확인
    log.Println("byte 길이:", len(protoBytes))
    log.Println("byte 내용:", protoBytes)

    // 직렬화한 bytes 의 내용을 파일에 저장
    err = os.WriteFile("../bytes_from_go", protoBytes, os.ModePerm)
    if err != nil {
        log.Fatal(err.Error())
    }

    // 파일에서 bytes 의 내용을 읽어옴
    protobufBytesReadFromFile, err := os.ReadFile("../bytes_from_python")
    if err != nil {
        log.Fatal(err.Error())
    }

    // 읽어온 bytes 의 내용을 새 object 에 할당
    p2 := new(pb.Product)
    if err := proto.Unmarshal(protobufBytesReadFromFile, p2); err != nil {
        log.Fatal(err.Error())
    }

    // 새로 할당한 object 의 내용 확인
    log.Println(p2)

    // python 이 생성한 바이트와 go 가 생성한 바이트 비교
    log.Println("string(protoBytes) == string(protobufBytesReadFromFile):", string(protoBytes) == string(protobufBytesReadFromFile))
}
$ go run main.go
byte 길이: 12
byte 내용: [8 1 18 6 234 176 128 235 176 169 24 1]
id:1  name:"가방"  display:true
string(protoBytes) == string(protobufBytesReadFromFile): true

5. message 필드 변경 및 서로 다른 message 호환성 확인

  • protobuf message 의 필드를 수정하고, 수정한 message 가 서로 호환되는지 확인

5.1. proto 파일 수정

syntax = "proto3";

option go_package="product/pb";

message Product {
    int32 id = 1;
    string name = 2;
    bool display = 3;
}

// 필드 이름이 변경된 경우
message ProductFieldNameChanged {
    int32 id = 1;
    string title = 2;
    bool display = 3;
}

// 필드가 제거된 경우
message ProductFieldDeleted {
    int32 id = 1;
    // string name = 2;
    bool display = 3;
}

// 필드가 추가된 경우
message ProductFieldAdded {
    int32 id = 1;
    string name = 2;
    bool display = 3;
    float discount_rate = 4;
}

// 필드가 추가, 제거된 경우
message ProductFieldAddedAndDeleted {
    int32 id = 1;
    // string name = 2;
    bool display = 3;
    float discount_rate = 4;
}

5.2. 테스트 1 - Go

  • Go code 생성
$ cd ~/myproj/mygo && protoc -I $HOME/myproj --go_out=. --go-grpc_out=. product.proto
  • Go code 수정
package main

import (
	"log"
	"mygo/product/pb"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
)

type IProtoStruct interface {
	ProtoReflect() protoreflect.Message
}

func getProtoBytes(p IProtoStruct) ([]byte, error) {
	return proto.Marshal(p.ProtoReflect().Interface())
}

func main() {
	log.SetFlags(0)

	// 변경 전 message type 의 bytes
	pBytes, err := getProtoBytes(&pb.Product{
		Id:      1,
		Name:    "가방",
		Display: true,
	})
	if err != nil {
		log.Fatal(err.Error())
	}

	// 필드 이름이 바뀐 message type 의 bytes
	pFieldNameChangedBytes, err := getProtoBytes(&pb.ProductFieldNameChanged{
		Id:      2,
		Title:   "상의",
		Display: true,
	})
	if err != nil {
		log.Fatal(err.Error())
	}

	// 필드가 제거된 message type 의 bytes
	pFieldDeletedBytes, err := getProtoBytes(&pb.ProductFieldDeleted{
		Id:      3,
		Display: true,
	})
	if err != nil {
		log.Fatal(err.Error())
	}

	// 필드가 추가된 message type 의 bytes
	pFieldAddedBytes, err := getProtoBytes(&pb.ProductFieldAdded{
		Id:           4,
		Name:         "모자",
		Display:      true,
		DiscountRate: 0.1,
	})
	if err != nil {
		log.Fatal(err.Error())
	}

	// 필드가 추가, 제거된 message type 의 bytes
	pFieldAddedAndDeletedBytes, err := getProtoBytes(&pb.ProductFieldAddedAndDeleted{
		Id:           5,
		Display:      true,
		DiscountRate: 0.2,
	})
	if err != nil {
		log.Fatal(err.Error())
	}

	// 다음 상황을 가정한 테스트
	// proto 파일이 수정 된 후
	// 서버는 수정된 내용이 코드에 반영됨
	// 클라이언트는 수정된 내용이 코드에 반영되지 않았음
	// 클라이언트가 수정 전 형식으로 bytes 를 보내올 때
	// 서버가 수신한 bytes 를 변환할 수 있는가
	p1 := new(pb.ProductFieldNameChanged)
	err = proto.Unmarshal(pBytes, p1)
	if err != nil {
		log.Println("pBytes 는 새로운 message type ProductFieldNameChanged 과 호환되지 않음")
	} else {
		log.Println("pBytes 는 새로운 message type ProductFieldNameChanged 과 호환됨")
		log.Println(p1)
	}

	p2 := new(pb.ProductFieldDeleted)
	err = proto.Unmarshal(pBytes, p2)
	if err != nil {
		log.Println("pBytes 는 새로운 message type ProductFieldDeleted 과 호환되지 않음")
	} else {
		log.Println("pBytes 는 새로운 message type ProductFieldDeleted 과 호환됨")
		log.Println(p2)
	}

	p3 := new(pb.ProductFieldAdded)
	err = proto.Unmarshal(pBytes, p3)
	if err != nil {
		log.Println("pBytes 는 새로운 message type ProductFieldAdded 과 호환되지 않음")
	} else {
		log.Println("pBytes 는 새로운 message type ProductFieldAdded 과 호환됨")
		log.Println(p3)
	}

	p4 := new(pb.ProductFieldAddedAndDeleted)
	err = proto.Unmarshal(pBytes, p4)
	if err != nil {
		log.Println("pBytes 는 새로운 message type ProductFieldAddedAndDeleted 과 호환되지 않음")
	} else {
		log.Println("pBytes 는 새로운 message type ProductFieldAddedAndDeleted 과 호환됨")
		log.Println(p4)
	}

	// 다음 상황을 가정한 테스트
	// proto 파일이 수정 된 후
	// 서버는 수정된 내용이 코드에 반영되지 않았음
	// 클라이언트는 수정된 내용이 코드에 반영됨
	// 클라이언트가 수정된 형식으로 bytes 를 보내올 때
	// 서버가 수신한 bytes 를 변환할 수 있는가
	p5 := new(pb.Product)
	err = proto.Unmarshal(pFieldNameChangedBytes, p5)
	if err != nil {
		log.Println("pFieldNameChangedBytes 는 이전 message type 과 호환되지 않음")
	} else {
		log.Println("pFieldNameChangedBytes 는 이전 message type 과 호환됨")
		log.Println(p5)
	}

	p6 := new(pb.Product)
	err = proto.Unmarshal(pFieldDeletedBytes, p6)
	if err != nil {
		log.Println("pFieldDeletedBytes 는 이전 message type 과 호환되지 않음")
	} else {
		log.Println("pFieldDeletedBytes 는 이전 message type 과 호환됨")
		log.Println(p6)
	}

	p7 := new(pb.Product)
	err = proto.Unmarshal(pFieldAddedBytes, p7)
	if err != nil {
		log.Println("pFieldAddedBytes 는 이전 message type 과 호환되지 않음")
	} else {
		log.Println("pFieldAddedBytes 는 이전 message type 과 호환됨")
		log.Println(p7)
	}

	p8 := new(pb.Product)
	err = proto.Unmarshal(pFieldAddedAndDeletedBytes, p8)
	if err != nil {
		log.Println("pFieldAddedAndDeletedBytes 는 이전 message type 과 호환되지 않음")
	} else {
		log.Println("pFieldAddedAndDeletedBytes 는 이전 message type 과 호환됨")
		log.Println(p8)
	}
}
  • 출력 결과
$ go run main.go
pBytes 는 새로운 message type ProductFieldNameChanged 과 호환됨
id:1 title:"가방" display:true
pBytes 는 새로운 message type ProductFieldDeleted 과 호환됨
id:1 display:true 2:"가방"
pBytes 는 새로운 message type ProductFieldAdded 과 호환됨
id:1 name:"가방" display:true
pBytes 는 새로운 message type ProductFieldAddedAndDeleted 과 호환됨
id:1 display:true 2:"가방"
pFieldNameChangedBytes 는 이전 message type 과 호환됨
id:2 name:"상의" display:true
pFieldDeletedBytes 는 이전 message type 과 호환됨
id:3 display:true
pFieldAddedBytes 는 이전 message type 과 호환됨
id:4 name:"모자" display:true 4:0x3dcccccd
pFieldAddedAndDeletedBytes 는 이전 message type 과 호환됨
id:5 display:true 4:0x3e4ccccd

5.3. 테스트 2 - Python

  • 같은 내용을 테스트한 python 코드 와 출력 결과
from product.product_pb2 import Product
from product.product_pb2 import ProductFieldNameChanged, ProductFieldDeleted, ProductFieldAdded, ProductFieldAddedAndDeleted
from google.protobuf.message import DecodeError


# 변경 전 message type 의 bytes
p_bytes: bytes = Product(
    id=1,
    name="가방",
    display=True,
).SerializePartialToString()


# 필드 이름이 바뀐 message type 의 bytes
p_field_name_changed_bytes: bytes = ProductFieldNameChanged(
    id=2,
    title="상의",
    display=True,
).SerializePartialToString()

# 필드가 제거된 message type 의 bytes
p_field_deleted_bytes: bytes = ProductFieldDeleted(
    id=3,
    display=True,
).SerializePartialToString()

# 필드가 추가된 message type 의 bytes
p_field_added_bytes: bytes = ProductFieldAdded(
    id=4,
    name="모자",
    display=True,
    discount_rate=0.1,
).SerializePartialToString()

# 필드가 추가, 제거된 message type 의 bytes
p_field_added_and_deleted: bytes = ProductFieldAddedAndDeleted(
    id=5,
    display=True,
    discount_rate=0.2,
).SerializePartialToString()

# 다음 상황을 가정한 테스트
# proto 파일이 수정 된 후
# 서버는 수정된 내용이 코드에 반영됨
# 클라이언트는 수정된 내용이 코드에 반영되지 않았음
# 클라이언트가 수정 전 형식으로 bytes 를 보내올 때
# 서버가 수신한 bytes 를 변환할 수 있는가
p1: ProductFieldNameChanged = ProductFieldNameChanged()
try:
    p1.ParseFromString(p_bytes)
except DecodeError as e:
    print("p_bytes 는 새로운 message type ProductFieldNameChanged 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_bytes 는 새로운 message type ProductFieldNameChanged 과 호환됨")
    print(p1)

p2: ProductFieldDeleted = ProductFieldDeleted()
try:
    p2.ParseFromString(p_bytes)
except DecodeError as e:
    print("p_bytes 는 새로운 message type ProductFieldNameChanged 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_bytes 는 새로운 message type ProductFieldNameChanged 과 호환됨")
    print(p2)

p3: ProductFieldAdded = ProductFieldAdded()
try:
    p3.ParseFromString(p_bytes)
except DecodeError as e:
    print("p_bytes 는 새로운 message type ProductFieldAdded 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_bytes 는 새로운 message type ProductFieldAdded 과 호환됨")
    print(p3)

p4: ProductFieldAddedAndDeleted = ProductFieldAddedAndDeleted()
try:
    p4.ParseFromString(p_bytes)
except DecodeError as e:
    print("p_bytes 는 새로운 message type ProductFieldAddedAndDeleted 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_bytes 는 새로운 message type ProductFieldAddedAndDeleted 과 호환됨")
    print(p4)

# 다음 상황을 가정한 테스트
# proto 파일이 수정 된 후
# 서버는 수정된 내용이 코드에 반영되지 않았음
# 클라이언트는 수정된 내용이 코드에 반영됨
# 클라이언트가 수정된 형식으로 bytes 를 보내올 때
# 서버가 수신한 bytes 를 변환할 수 있는가
p5: Product = Product()
try:
    p5.ParseFromString(p_field_name_changed_bytes)
except DecodeError as e:
    print("p_field_name_changed_bytes 는 이전 message type 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_field_name_changed_bytes 는 이전 message type 과 호환됨")
    print(p5)

p6: Product = Product()
try:
    p6.ParseFromString(p_field_deleted_bytes)
except DecodeError as e:
    print("p_field_deleted_bytes 는 이전 message type 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_field_deleted_bytes 는 이전 message type 과 호환됨")
    print(p6)

p7: Product = Product()
try:
    p7.ParseFromString(p_field_added_bytes)
except DecodeError as e:
    print("p_field_added_bytes 는 이전 message type 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_field_added_bytes 는 이전 message type 과 호환됨")
    print(p7)

p8: Product = Product()
try:
    p8.ParseFromString(p_field_added_and_deleted)
except DecodeError as e:
    print("p_field_added_and_deleted 는 이전 message type 과 호환되지 않음")
except Exception as e:
    print(e)
else:
    print("p_field_added_and_deleted 는 이전 message type 과 호환됨")
    print(p8)
(venv) $ python main.py
p_bytes 는 새로운 message type ProductFieldNameChanged 과 호환됨
id: 1
title: "가방"
display: true

p_bytes 는 새로운 message type ProductFieldNameChanged 과 호환됨
id: 1
display: true

p_bytes 는 새로운 message type ProductFieldAdded 과 호환됨
id: 1
name: "가방"
display: true

p_bytes 는 새로운 message type ProductFieldAddedAndDeleted 과 호환됨
id: 1
display: true

p_field_name_changed_bytes 는 이전 message type 과 호환됨
id: 2
name: "상의"
display: true

p_field_deleted_bytes 는 이전 message type 과 호환됨
id: 3
display: true

p_field_added_bytes 는 이전 message type 과 호환됨
id: 4
name: "모자"
display: true

p_field_added_and_deleted 는 이전 message type 과 호환됨
id: 5
display: true
profile
시간이 만든 코드

0개의 댓글