互換性

このページでは、バージョニング セクションに示されている、互換性を損なう変更と互換性を損なわない変更のリストについて、より詳細に説明します。

何が互換性を損なう(互換性のない)変更としてみなされるかは、必ずしも明確ではありません。ここでのガイダンスは、あらゆる可能な変更の包括的なリストではなく、指針として扱うことが推奨されます。

ここにリストされているルールは、クライアントの互換性にのみ関係しています。API プロデューサーは、実装の詳細の変更を含め、デプロイについて独自の要件を認識していることが期待されます。

一般的な目的は、新しいマイナー バージョンやパッチに更新するサービスによってクライアントが破壊されないようにすることです。考慮する破壊には次の種類があります。

  • ソースの互換性: 1.0 に対して書かれたコードを 1.1 に対してコンパイルできない
  • バイナリ互換性: 1.0 に対してコンパイルされたコードを 1.1 クライアント ライブラリに対してリンクまたは実行できない(厳密な詳細はクライアント プラットフォームによって異なります。これはさまざまな状況で変わることがあります)
  • ワイヤの互換性: 1.0 に対して構築されたアプリケーションが 1.1 サーバーと通信できない
  • セマンティックの互換性: すべてが実行されるが、意図しない結果や意外な結果となる

言い換えると、古いクライアントが、同じメジャー バージョン番号内の新しいサーバーに対して動作できる必要があり、新しいマイナー バージョンに更新(新しい機能を活用するためなどに)したくなったときに、簡単に更新できる必要があります。

注: v1.1 や v1.0 などのバージョン番号を指す場合、具体的なバージョンではない論理バージョン番号を指しています。これらは単に変更を説明しやすくするためのものです。

プロトコル ベースの理論的な考慮事項に加えて、生成されたコードと手書きコードの両方を含むクライアント ライブラリが存在することによる実際的な考慮事項があります。検討している変更について、可能な場合は常に、新しいバージョンのクライアント ライブラリを生成し、それらがテストに引き続き合格することを確認するテストを実施してください。

以下の説明では、proto メッセージを 3 つのカテゴリに分類します。

  • リクエスト メッセージ(GetBookRequest など)
  • レスポンス メッセージ(ListBooksResponse など)
  • リソースメッセージ(Book など、他のリソース メッセージ内で使用されるメッセージを含む)

これらのカテゴリには異なるルールがあります。リクエスト メッセージはクライアントからサーバーに送信されるのみで、レスポンス メッセージはサーバーからクライアントに送信されるのみですが、リソース メッセージは通常は両方向に送信されます。 特に、更新可能なリソースは、読み取り / 変更 / 書き込みサイクルの観点から考慮する必要があります。

下位互換性のある(互換性を損なわない)変更

API サービス定義への API インターフェースの追加

プロトコルの観点からは、これは常に安全です。唯一の注意点は、手書きコード内の新しい API インターフェース名がクライアント ライブラリですでに使用されている可能性があることです。新しいインターフェースが既存のインターフェースと関連性がない場合、この可能性は低くなります。新しいインターフェースが既存のインターフェースの簡易版である場合は、競合が発生する可能性が高くなります。

API インターフェースへのメソッドの追加

クライアント ライブラリですでに生成されているメソッドと競合するメソッドを追加しない限り、このことは問題ありません。

互換性を損なう可能性があるのは、次の例のような場合です。GetFoo メソッドを使用している場合、C# コード ジェネレータによって、すでに GetFooGetFooAsync メソッドが生成されています。このため、クライアント ライブラリの観点からは、API インターフェースに GetFooAsync メソッドを追加することは、互換性を損なう変更になります。

メソッドへの HTTP バインディングの追加

バインディングによって曖昧さが発生しないと仮定すると、以前に拒否していた URL にサーバーが応答するようにすることは安全です。これは、既存のオペレーションに新しいリソース名パターンを適用するときに行われる可能性があります

リクエスト メッセージへのフィールドの追加

リクエスト フィールドの追加は、フィールドを指定しないクライアントが新しいバージョンでも古いバージョンと同じように扱われる限り、互換性を損なうことはありません。

最も明白な正しくない例として、ページ分けがあります。API の v1.0 にコレクションのページ分けが含まれていない場合、デフォルトの page_size が無限として扱われない限り、V1.1 にコレクションのページ分けを追加することはできません(これは一般的には良くないアイデアです)。それ以外の場合は、単一のリクエストから完全な結果を得ることを想定している v1.0 クライアントが、コレクションにより多くのリソースが含まれているという認識なしに、省略された結果を受け取ることがあります。

レスポンス メッセージへのフィールドの追加

リソース以外のレスポンス メッセージ(ListBooksResponse など)は、他のレスポンス フィールドの動作を変更しない限り、クライアントを中断することなく展開できます。以前にレスポンスで入力されていたフィールドは、冗長性が導入されても、引き続き同じセマンティクスで入力されます。

たとえば、1.0 のクエリ レスポンスには、重複のために一部の結果が省略されたことを示す contained_duplicates のブール フィールドがあるとします。1.1 では、duplicate_count フィールドでより詳細な情報が提供されています。1.1 の観点から見ると冗長になりますが、contained_duplicates フィールドには、引き続き値が入力されていることが必須です。

列挙型への値の追加

リクエスト メッセージでのみ使用される列挙型は、自由に拡張して新しい要素を含めることができます。たとえば、リソースビュー パターンを使用すると、新しいビューを新しいマイナー バージョンに追加できます。クライアントはこの列挙型を受け取る必要はないため、処理しない値を認識する必要はありません。

リソース メッセージとレスポンス メッセージについては、デフォルトの前提として、クライアントは認識していない列挙値を処理する必要があります。ただし、API プロデューサーは、新しい列挙要素を正しく処理するアプリケーションを作成することは難しい場合があることを認識する必要があります。API オーナーは、不明な列挙値が発生した場合のクライアントの予想される動作をドキュメント化することが推奨されます。

Proto3 では、クライアントは認識していない値を受け取り、同じ値を維持しながらメッセージを再シリアル化できるため、読み取り / 変更 / 書き込みサイクルが中断されません。JSON 形式では、値の「名前」が不明な数値を送信することが許容されますが、サーバーでは、通常、クライアントが特定の値を実際に認識しているかどうかはわかりません。このため、JSON クライアントは以前は不明だった値を受け取ったことを認識できますが、認識するのは名前と数値のどちらか一方のみです。両方を認識することはありません。 同じ値を読み取り / 変更 / 書き込みサイクルでサーバーに戻した場合、サーバーは両方の形式を認識する必要があるため、そのフィールドは変更されません。

出力専用リソース フィールドの追加

サーバーによって提供されるのみの、リソース エンティティ内のフィールドを追加できます。サーバーはリクエスト内のクライアント提供の値が有効であることを検証できますが、値が省略されたかどうかは検証する必要があります。

下位互換性のない(互換性を損なう)変更

サービス、フィールド、メソッド、列挙値の削除や名前変更

基本的に、クライアント コードによって参照される可能性のあるものについて、削除したり名前を変更したりすることは互換性を損なう変更となり、メジャー バージョンを増加させる必要があります。古い名前を参照するコードにより、一部の言語(C# や Java など)ではコンパイル時にエラーが発生し、他の言語では実行時にエラーが発生したり、データが失われたりする可能性があります。送信形式の互換性はここでは関係ありません。

HTTP バインディングの変更

ここでの「変更」は、事実上「削除と追加」です。たとえば、PATCH をサポートする必要があるが、公開版では PUT がサポートされている場合や、誤ったカスタム動詞名を使用した場合、新しいバインディングを追加できますが、サービス メソッドを削除することが互換性を損なう変更になるのと同じ理由から、古いバインディングを削除することはできません

フィールドのタイプの変更

新しい形式が送信形式に対応している場合でも、これによりクライアント ライブラリの生成コードが変更される可能性があるため、メジャー バージョンを増加させる必要があります。コンパイル済みの、静的に型指定された言語では、コンパイル時にエラーが発生しやすくなります。

リソース名の形式の変更

リソース名は変更できません。つまり、コレクション名は変更できません。

互換性を損なうほとんどの変更とは異なり、このことはメジャー バージョンにも影響します。クライアントが v1.0 で作成されたリソースに v2.0 でアクセスすることが予想される場合、またはその逆の場合、両方のバージョンで同じリソース名を使用するようにします。

より詳細には、次の理由から、有効なリソース名のセットも変更しないことが推奨されます。

  • より限定的になると、以前は成功していたリクエストが失敗するようになります。
  • 以前にドキュメント化されたものよりも限定的でなくなった場合、以前のドキュメント化に基づいて前提条件を設定しているクライアントが破損する可能性があります。クライアントは、多くの場合、許可された文字セットと名前の長さに厳密な方法で、他の場所にリソース名を格納します。または、クライアントはドキュメントに従うために独自のリソース名検証を実行する可能性があります(たとえば、より長い EC2 リソース ID を使用できるようにした際に、Amazon はお客様に多くの警告を示し、移行期間を設けました)。

このような変更は、proto のドキュメントによってのみ示されるので注意してください。 このため、破損のために CL を見直すときは、非コメントの変更を見直すだけでは不十分です。

既存のリクエストの可視動作の変更

動作が明示的にサポートされていない場合やドキュメント化されていない場合でも、クライアントは多くの場合、API の動作とセマンティクスに依存します。したがって、ほとんどの場合、API データの動作やセマンティクスを変更することは、ユーザーからは破壊とみなされます。動作が暗号的に隠されていない場合は、ユーザーがその動作を見つけて依存すると想定することが推奨されます。

この理由から、ユーザーが独自のトークンを作成し、トークンの動作が変更されたときに破壊されないように、(データの内容に関係なく)ページ設定トークンを暗号化することをおすすめします。

HTTP 定義での URL 形式の変更

上記のリソース名の変更の他に、ここでは 2 種類の変更について検討します。

  • カスタム メソッド名: リソース名の一部ではありませんが、カスタム メソッド名は REST クライアントによって送信される URL の一部です。カスタム メソッド名を変更しても gRPC クライアントは破損しませんが、パブリック API は REST クライアントを持っていると想定する必要があります。
  • リソース パラメータ名: v1/shelves/{shelf}/books/{book} から v1/shelves/{shelf_id}/books/{book_id} への変更は、置き換えられたリソース名には影響しませんが、コード生成に影響する可能性があります。

リソース メッセージへの読み取り / 書き込みフィールドの追加

クライアントは多くの場合、読み取り / 変更 / 書き込みオペレーションを実行します。ほとんどのクライアントは、認識しないフィールドに値を提供しません。特に proto3 ではこれをサポートしていません。プリミティブ型ではなくメッセージ型の欠落フィールドがある場合、これらのフィールドに更新が適用されないことを指定できますが、これにより、そのようなフィールド値をエンティティから明示的に削除することが難しくなります。 proto3 では、int32 フィールドを明示的に 0 を指定することと指定しないことには違いがないため、プリミティブ型(stringbytes を含む)を単純にこのように処理することはできません。

すべての更新がフィールド マスクを使用して実行される場合は、このことは問題ではありません。これは、クライアントが認識しないフィールドはクライアントによって暗黙的に上書きされないためです。ただし、この決定は通常の API とは異なります。ほとんどの API では「リソース全体」の更新が許可されます。