Protocol Buffersのoneofを使う【Go + gRPC】

[Protocol Buffers](https://developers.google.com/protocol-buffers)のoneofを使って実装しようとしたときに微妙に悩んだので、使用方法を備忘録として残しておきます。

Protocol Buffers、Go、gRpc、どれも触り始めて数ヶ月なので、変な箇所がありましたら指摘していただけると大変助かります。

## oneofとは

あるメッセージで複数のフィールドを定義して、「このフィールドのうち、**最大でどれか1個だけ**がセットされてるよ！」と宣言できる機能です。

```
message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}
```

上記のようにメッセージを定義すると、「`SampleMessage`には`name`フィールドか`sub_message`フィールドのどちらかが入っている。どちらのフィールドも入ってない場合もある」ということになります。
ただし、`name`と`sub_message`の両方が入っていることはありません。
なお、`repeated`なフィールドはoneof内では使えません。

oneof内で宣言されているフィールドは共有メモリを使うので、メモリの節約ができるそうです。
また、メッセージの一部を共通化したいときとか、
1つのAPIで異なるフィールドを扱いたいときに便利です。
あと、proto3で`optional`が使えなくなったので、[optionalの代わりに使う](https://qiita.com/disc99/items/a8ac2a264f322bc6d6e5)という方法も提示されてます。

## protoの作成

以下、oneofを使ったprotoファイルの例です。

```protobuf
// testOneOf.proto

// ...(略)

message OneOf{
    oneof name{
        int32 id = 1;
        string first_name = 2;
        string nickname = 3;
    }
}

message Name{
    string name = 1;
}

service TestOneOf {
    rpc Get(google.protobuf.Empty) returns (OneOf) {
        option (google.api.http) = {
            get: "/oneof"
        };
    }

    rpc Post(OneOf) returns (Name) {
        option (google.api.http) = {
            post: "/oneof"
            body: "*"
        };
    }
}
```

`OneOf`メッセージの中でoneofを使っています。
`OneOf`メッセージの中身は、以下のうちのどれか一つになります。

* int型の`id`フィールド
* string型の`first_name`フィールド
* string型の`nickname`フィールド
* フィールドなし

今回はフィールドなしの場合は置いとこうと思います。

`Get`を実行すると空のメッセージか`id`か`first_name`か`nickname`が入ってるメッセージを返すよ、
`Post`を実行するときは上記のうちのどれかを送信してね、という感じです。
ちなみに、`Post`実行時はリクエストで渡された値をstring型にして出力するように実装しようと思います。

作成したprotoファイルを`protoc`でコンパイルし、実装していきます。

## oneofを使ってレスポンスを返す

oneofを使ってレスポンスを返すときは以下のように実装します。

```go
// testOneOf.go

// ...(略)

// Get oneofを使ってレスポンスを返す
func (t *TestOneOf) Get(ctx context.Context, in *empty.Empty) (*proto.OneOf, error) {
	return &proto.OneOf{
		Name: &proto.OneOf_FirstName{
			FirstName: "Andy",
		},
	}, nil
}
```

上記のように実装してAPIを実行すると、以下のようなレスポンスが返ってきます。
(私の環境ではgrpc-gatewayを使用しているので、curlコマンドからAPIを実行しました。)

```
$ curl -X GET "http://localhost/oneof"
{"first_name":"Andy"}
```

`first_name`が返ってきましたね！

上記`Get`関数の`return`部分を以下のように変えると、int型の`Id`を返すことができます。

```go
return &proto.OneOf{
    Name: &proto.OneOf_Id{
        Id: 123,
    },
}, nil
```

変更後のAPI実行結果は以下の通り。

```
$ curl -X GET "http://localhost/oneof"
{"id":123}
```

フィールドなしのメッセージを返したい場合は以下のように変更します。

```go
return &proto.OneOf{}, nil
```

変更後のAPI実行結果は以下の通り。

```
$ curl -X GET  "http://localhost/oneof"
{}
```

### oneofフィールドの構造体

ところで「`return`文で使ってる`OneOf_FirstName`ってどこから来たんだ、そんな構造体protoファイルで宣言してないぞ」となりませんでしたか？
私はなりました。

そこで、コンパイルで自動生成された`*.pg.go`ファイルの中身をちょっと覗いてみました。

```go
// testOneOf.pb.go

// ...(略)

type OneOf struct {
	// Types that are valid to be assigned to Name:
	//	*OneOf_Id
	//	*OneOf_FirstName
	//	*OneOf_Nickname
	Name                 isOneOf_Name `protobuf_oneof:"name"`
	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
	XXX_unrecognized     []byte       `json:"-"`
	XXX_sizecache        int32        `json:"-"`
}

// ...(略)

type isOneOf_Name interface {
	isOneOf_Name()
}

type OneOf_Id struct {
	Id int32 `protobuf:"varint,1,opt,name=id,proto3,oneof"`
}

type OneOf_FirstName struct {
	FirstName string `protobuf:"bytes,2,opt,name=first_name,json=firstName,proto3,oneof"`
}

type OneOf_Nickname struct {
	Nickname string `protobuf:"bytes,3,opt,name=nickname,proto3,oneof"`
}

// ...(略)

func (*OneOf_Id) isOneOf_Name() {}

func (*OneOf_FirstName) isOneOf_Name() {}

func (*OneOf_Nickname) isOneOf_Name() {}

// ...(略)
```

構造体`OneOf_FirstName`が自動生成されてました。
`OneOf.Name`には`isOneOf_Name`というインタフェース型を指定するようになっており、自動生成された構造体を動的に扱えるようになっていますね。
こういう使い方もできるんだなあ、Goのインタフェース…。
これに気付かず、いざoneofを使おう！というときに「どうやって`first_name`返したらいいんだこれ？？？」とちょっと悩んでしまいました。。。

[公式ドキュメント](https://developers.google.com/protocol-buffers/docs/reference/go-generated#oneof)には「oneof内のフィールドごとに構造体が生成されるよ！」とちゃんと書いてあります。

> For a oneof field, the protobuf compiler generates a single field with an interface type isMessageName_MyField. It also generates a struct for each of the singular fields within the oneof. These all implement this isMessageName_MyField interface.

やっぱりドキュメントは読まないといけない。


## oneofを使ってリクエストを受け付ける

リクエストを受け付けるときは以下のように実装します。

```go
// testOneOf.go

// ...(略)

// Post oneofを使ってリクエストを受け付ける
func (t *TestOneOf) Post(ctx context.Context, in *proto.OneOf) (*proto.Name, error) {
	name := in.GetName()

	switch x := name.(type) {
	case *proto.OneOf_Id:
		return &proto.Name{
			Name: strconv.Itoa(int(x.Id)),
		}, nil
	case *proto.OneOf_FirstName:
		return &proto.Name{
			Name: x.FirstName,
		}, nil
	case *proto.OneOf_Nickname:
		return &proto.Name{
			Name: x.Nickname,
		}, nil
	default:
		return nil, nil
	}
}
```

引数`in`から`GetName()`を使って`oneof name`の部分にあたる値を取り出し、
型スイッチでどの構造体が使われているか判定して、しかるべき処理をするという感じです。

上記のように実装してAPIを実行してみます。

```
$ curl -X POST "http://localhost/oneof" -d '{"first_name": "Bob"}'
{"name":"Bob"}
```

`first_name`として送信したBobが返ってきました！

`nickname`でも同様です。

```
$ curl -X POST "http://localhost/oneof" -d '{"nickname": "Bobby"}'
{"name":"Bobby"}
```

int型の`id`でも返ってきました。

```
$ curl -X POST "http://localhost/oneof" -d '{"id": 123}'
{"name":"123"}
```

型スイッチで`nil`だったときの処理をちゃんと実装していないので、空のメッセージを送信するとエラーになります。

```
$ curl -X POST "http://local.yj-webapp.xyz/oneof" -d '{}'
{"error":"grpc: error while marshaling: proto: Marshal called with nil","code":13,"error_details":null}
```

### oneof内フィールドの取得

`Post`の実装で`GetName()`というメソッドを使ってます。
これもコンパイル時に自動生成されたものなので、`*.pb.go`を覗いて中で何をやってるメソッドなのかを確認してみました。

```go
// testOneOf.pb.go

// ...(略)

func (m *OneOf) GetName() isOneOf_Name {
	if m != nil {
		return m.Name
	}
	return nil
}

// ...(略)
```

`OneOf`メッセージ内の`Name`を返してます。まあそうだろうなという感じです。
このくらい自力でも実装できるでしょうが、こうやって自動生成で用意してくれるのは手間が省けてありがたいなと思いました。

以下のように、`OneOf`メッセージから直で`Id`とかを返してくれるメソッドも用意されてるみたいです。

```go
// testOneOf.pb.go

// ...(略)

func (m *OneOf) GetId() int32 {
	if x, ok := m.GetName().(*OneOf_Id); ok {
		return x.Id
	}
	return 0
}

func (m *OneOf) GetFirstName() string {
	if x, ok := m.GetName().(*OneOf_FirstName); ok {
		return x.FirstName
	}
	return ""
}

func (m *OneOf) GetNickname() string {
	if x, ok := m.GetName().(*OneOf_Nickname); ok {
		return x.Nickname
	}
	return ""
}

// ...(略)
```

直で取得したいな！という場面があるかどうかわからないですが便利。たぶん。

## 参考文献

* [Protocol Buffersガイド](https://developers.google.com/protocol-buffers/docs/overview)
    * [Language Guide (proto3)](https://developers.google.com/protocol-buffers/docs/proto3)
* [Protocol Buffersリファレンス](https://developers.google.com/protocol-buffers/docs/reference/overview)
    * [Go Reference > Go Generated Code](https://developers.google.com/protocol-buffers/docs/reference/go-generated)
* [Protocol Buffers の oneof で message の形式変更に耐える](http://innossh.hatenablog.com/entry/2019/01/27/155405)
* [Protocol Buffers(proto3)でoptionalをどう扱うか](https://qiita.com/disc99/items/a8ac2a264f322bc6d6e5)