📘

protocol buffersのEnumの定数名の変更方法を検討する(gRPC)

2022/06/30に公開

結論から言うと allow_aliasを利用しました。

You can define aliases by assigning the same value to different enum constants. To do this you need to set the allow_alias option to true, otherwise the protocol compiler will generate an error message when aliases are found.

https://developers.google.com/protocol-buffers/docs/proto3#enum

はじめに

gRPCの通信で利用するProtocol BuffersではEnumが存在します。
Enumはmessageタイプを定義するときに特定のフィールドに事前定義した値のうち一つだけ指定したい場合に利用するものです。
e.g. https://developers.google.com/protocol-buffers/docs/proto3#enum より抜粋

  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }

今回はEnumで定義した定数名、上記例で言うUNIVERSALやWEBをリネームする際に検討した内容を書き出します。
※前提条件としてserver/clientともにGoのコードを生成し利用しています。

検討したこと

以下の3つの方法を検討しました。

  1. Enumに追加で定数を定義しそちらに置き換える(Add)
  2. 変更したい定義名を直接書き換える。(Rename)
  3. allow_aliasを使って異なる定数名に同じ値を割り当てる(Alias)

「1. Enumに追加で定数を定義しそちらに置き換える(Add)」について

Enumに新しい定数を追加し、そちらを参照してもらう。
まず、server側は旧定数と新定数で同じ動きをするように修正する必要がある。
client側はserver側の修正後にあまり考えずに新定数に置き換えることができる。

prosは、古い定数の後をproto上に残すことができること。
consは、server側の修正コストが大きくなりがちなこと。server側とclient側でリリース調整が必要なこと。

「2. 変更したい定義名を直接書き換える。(Rename)」について

protobuf形式を利用している場合、値のみ送信されるため動作自体は問題ない。
protocで生成したコードを取り込んだときにbuild errorが発生する可能性がある。

pros/consは1のAddと逆になる。

「3.allow_aliasを使って異なる定数名に同じ値を割り当てる(Alias)」について

2.Renameの上位互換。
同じ値に異なる定数名を割り当てられるため、Rename前の定数名も残す選択肢が取れる。buildエラーが起こらない。clientがaliasへの切り替えタイミングも任意。

よさそう。

allow_aliasで生成されるコードの差分を確認する。

  1. protoに allow_alias と新定数名を追加する。
myapp.proto
syntax = "proto3";
package myapp;
option go_package = "./;pb";

service Echo {
  rpc EchoNumber(EchoNumberRequest) returns (EchoNumberResponse);
}

message EchoNumberRequest {
  Number number = 1;
}

message EchoNumberResponse {
  Number number = 1;
}

enum Number {
  option allow_alias = true;
  ZERO    = 0;
  ONE     = 1;
  TOW     = 2;
+ TWO     = 2;  
}
  1. protoファイルをrecompileする
$ protoc --go_out=./pb --go_opt=paths=source_relative \
    --go-grpc_out=./pb --go-grpc_opt=paths=source_relative \
    myapp.proto

$ tree
.
├── myapp.proto
└── pb
    ├── myapp.pb.go
    └── myapp_grpc.pb.go

1 directory, 3 files
  1. 差分を確認する。Number_TWOが追加されている。
myapp.pb.go
@@ -26,6 +26,7 @@ const (
        Number_ZERO Number = 0
        Number_ONE  Number = 1
        Number_TOW  Number = 2
+       Number_TWO  Number = 2
 )

 // Enum value maps for Number.
@@ -34,11 +35,13 @@ var (
	Number_name = map[int32]string{
                0: "ZERO",
                1: "ONE",
                2: "TOW",
+               // Duplicate value: 2: "TWO",
        }
        Number_value = map[string]int32{
                "ZERO": 0,
                "ONE":  1,
                "TOW":  2,
+               "TWO":  2,
        }
 )

@@ -174,15 +177,16 @@ var file_myapp_proto_rawDesc = []byte{
(省略)
 }

3'. TOW = 2;に対して [deprecated = true]を追加してもNumber_nameで"TWO"がコメントアウトされる。単純にEnum内の定義順に依存する。

myapp.proto
@@ -18,6 +18,6 @@ enum Number {
   option allow_alias = true;
   ZERO = 0;
   ONE = 1;
-  TOW = 2;
+  TOW = 2[deprecated = true];
   TWO = 2;
 }
myapp.pb.go
@@ -25,8 +25,9 @@ type Number int32
 const (
        Number_ZERO Number = 0
        Number_ONE  Number = 1
-       Number_TOW  Number = 2
-       Number_TWO  Number = 2
+       // Deprecated: Do not use.
+       Number_TOW Number = 2
+       Number_TWO Number = 2
 )

 // Enum value maps for Number.
@@ -177,16 +178,17 @@ var file_myapp_proto_rawDesc = []byte{
(省略)
 }

Discussion