api
RPC
protobuf
ProtocolBuffers
microservices

Protocol Buffers v3でフィールドのrequiredという仕様が削除された話

Microservices Advent Calendar 2017 3日目の記事です。
今日は、2日目の記事で書いたロバストネス原則/Tolerant Readerに関連する話で、Protocol Buffersにあったフィールドのrequiredという仕様と、なぜそれが削除されたかについて書きます。

Protocol Buffersとは

Protocol Buffersはメッセージフォーマットの1つです。JSONと大きく違うのは、バイナリ形式であることと、型定義があることの2点です。

Protocol Buffersの理解には、 @tayama0324 さんの歌舞伎座tech#12での資料 がおすすめです。(実はこの資料の中に、本稿で書いてある話も大体載っていました)

今回はマイクロサービスの話なので、Provider/Consumerで型定義ファイルを共有し、Providerがシリアライズ, Consumerがデシリアライズするようなケースについて書いています。
(あえて抽象的に書いていますが、その最も普通の例は gRPC です。昨年の @disc99 さんの記事が参考になります。)

Protocol Buffers v2でのrequiredフィールド

v2の仕様では、フィールド定義にrequiredという修飾子をつけることが出来ました。
requiredが付いているフィールドは、そこに値が存在することが保証されます。型定義ファイルのあるフィールドにrequiredがついていると、そのフィールドの値が存在しないデータをシリアライズ/デシリアライズしようとすると失敗します。

アプリケーションでデータを扱うときは一般的に、そのフィールドの値がrequiredなのかoptionalなのかというのは有用な情報で、メッセージフォーマットに型があるならそこにその情報を含めたいというのは自然な発想だと思います。

requiredフィールドの問題点

既存のConsumerを壊さずに、requiredフィールドの追加/削除が出来ない というのが問題点です。

そもそもrequiredが表すことが何なのかを、もう少していねいに考えてみましょう。
Protocol Buffers v2のrequired宣言は、ProviderにもConsumerにもそれを強制するものでした。
しかし、基本的にschemaは、まずProviderが提供するものです。
つまり、本来requiredは Provider側からこのフィールドは必ず返すという宣言 であるべきであって、Consumerが必ず要求しているとは限りません。したがって、

  • requiredフィールドの追加で、既存のConsumerが壊れる
  • requiredフィールドの削除で、そのフィールドを実際には使っていないConsumerが壊れる

ということは、前日書いたロバストネス原則に反し、マイクロサービスの通信手段としては良くありません。

さらに、一般的にマイクロサービスでは「同時リリース」というのは出来ないので、Consumer側に何か手をいれるステップを踏んで段階的リリースできるならまだ良いのですが、そういう手段もありませんでした。

v2のドキュメントにもすでに、requiredフィールドを使わずにアプリケーションのレイヤでバリデーションをかけることを検討すべきという記述がありました。
そのような経緯があり、requiredフィールドはv3の仕様で削除されました。

代わりにどんな手段を用いるべきか

まず本来、requiredとして表現したかったことを、以下の2つに分割して考えましょう。

  1. Providerが、requiredであることをを保証する
  2. Consumerから、requiredであることを要求する

1はメッセージフォーマットのレイヤでは行わず、APIドキュメントなどに明示し、Provider側のコードでそれを保証します。
そして、このような制約は未来に変わる可能性もあることを認識します。変わるとすると、requiredであることを要求しているConsumerがいたら壊れるので、先にConsumerを変更する必要があります。
したがって、2については、 どのConsumerがrequiredであることを要求しているのかをProviderが知ることが出来る とよいでしょう。前日の記事でも言及したように、これは契約の概念を持ち出して表すことができます。これについてはまた後日書きたいと思います。

参考文献

Protocol Buffers (歌舞伎座tech#12) ( @tayama0324 )
REST APIの設計で消耗している感じたときのgRPC入門 ( @disc99 )
TolerantReader (Martin Fowler)
Language Guide  |  Protocol Buffers (proto2) (Google Developers) Required Is Foreverの項
why messge type remove 'required,optional'? · Issue #2497 · google/protobuf