一般的な設計パターン

空のレスポンス

標準の Delete メソッドでは、「ソフト」削除を行う場合を除き、google.protobuf.Empty が返される必要があります。「ソフト」削除の場合、このメソッドは、削除が進行中であることを示すように状態が更新されたリソースを返される必要があります。

カスタム メソッドでは、空の場合でも独自の XxxResponse メッセージを持つ必要があります。メソッドの機能が時間経過につれて拡張し、追加のデータを返さなければならない可能性が高まるためです。

範囲の表現

範囲を表すフィールドでは、命名規則 [start_xxx, end_xxx)[start_key, end_key)[start_time, end_time) など)に従って、半開区間を使用する必要があります。半開区間のセマンティクスは、一般的に C++ STL ライブラリと Java 標準ライブラリで使用されます。 API では、(index, count)[first, last] など、範囲を表すその他の方法の使用を避ける必要があります。

リソースラベル

リソース指向 API では、リソース スキーマは API によって定義されます。クライアントがリソースに少量のシンプルなメタデータをアタッチできるようにする(たとえば、仮想マシンリソースをデータベース サーバーとしてタグ付けする)には、API が map<string, string> labels フィールドをリソース定義に追加する必要があります

message Book {
  string name = 1;
  map<string, string> labels = 2;
}

長時間実行オペレーション

完了するまでに通常長い時間がかかる API メソッドは、長時間実行オペレーションのリソースをクライアントに返すように設計できます。クライアントはこれを使用して進行状況を追跡し、結果を受信できます。 Operation では、長時間実行オペレーションで動作する標準インターフェースを定義します。 整合性を維持するために、個々の API では、長時間実行オペレーションに対して独自のインターフェースを定義しないでください

オペレーション リソースはレスポンス メッセージとして直接返される必要があり、オペレーションの結果は API に反映されることが推奨されます。たとえば、リソースを作成するときは、そのリソースを List および GET メソッドで表示する必要がありますが、そのリソースが使用する準備が整っていないことが示されるようにする必要があります。オペレーションが完了したら、Operation.response フィールドには、メソッドが長時間実行されていない場合に直接返されるメッセージが含まれます。

オペレーションは、Operation.metadata フィールドを使用して進行状況に関する情報を提供できます。初期実装で metadata フィールドにデータが入力されていない場合でも、API でこのメタデータのメッセージを定義する必要があります。

リストのページ分割

リスト可能なコレクションは、結果が通常小さい場合でもページ分割をサポートすることが推奨されます。

根拠: API が最初からページ分割をサポートしていない場合は、ページ分割を追加すると API の動作に一貫性がなくなるため、後でサポートするとトラブルの原因になります。 API が現時点でページ分割を使用していることを認識していないクライアントは、実際には最初のページのみを受信した場合でも、完全な結果を受信したと誤って想定する可能性があります。

List メソッドでページ分割(リスト結果をページで返す)をサポートするには、API で以下を行う必要があります。

  • List メソッドのリクエストメッセージで string フィールド page_token を定義します。クライアントはこのフィールドを使用して、リスト結果の特定のページをリクエストします。
  • List メソッドのリクエストメッセージで int32 フィールド page_size を定義します。クライアントはこのフィールドを使用して、サーバーによって返される結果の最大数を指定します。1 ページ内に返される結果の最大数が、サーバーによってさらに制限されることがありますpage_size0 の場合、サーバーは返される結果の数を決定します。
  • List メソッドのレスポンス メッセージで string フィールド next_page_token を定義します。このフィールドは、結果の次のページを取得するためのページ設定トークンを表します。値が "" の場合、リクエストに対してそれ以上の結果がないことを意味します。

結果の次のページを取得するには、クライアントが後続の List メソッド呼び出しで、レスポンスの next_page_token の値を(リクエスト メッセージの page_token フィールド)で渡す必要があります。

rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);

message ListBooksRequest {
  string parent = 1;
  int32 page_size = 2;
  string page_token = 3;
}

message ListBooksResponse {
  repeated Book books = 1;
  string next_page_token = 2;
}

クライアントがページトークンに加えてクエリ パラメータを渡すときに、クエリ パラメータとページトークンに整合性がない場合、サービスによってリクエストを失敗させる必要があります。

ページトークンのコンテンツは、URL セーフの Base64 エンコードされたプロトコル バッファにすることが推奨されます。 これにより、互換性の問題なしにコンテンツを増やすことができます。 ページトークンに機密情報が含まれている可能性がある場合は、その情報を暗号化することが推奨されます。サービスでは、次のいずれかの方法で、ページトークンの改ざんによる意図しないデータの公開を防ぐ必要があります。

  • フォローアップ リクエストでクエリ パラメータを再指定を要求します。
  • ページトークン内のサーバー側のセッション状態のみを参照します。
  • ページトークンでクエリ パラメータを暗号化して署名し、呼び出しのたびにそれらのパラメータを再検証し、再承認します。

ページ分割を実装すると、total_size という名前の int32 フィールドを使用して項目の合計数が表示される場合があります

サブコレクションのリスト

場合によっては、サブコレクション間でのクライアントの List/Search を可能にする必要があります。たとえば、ライブラリ API に shelf のコレクションがあり、各 shelf に book のコレクションがあり、クライアントはすべての shelf の book を検索する必要があるとします。このような場合は、サブコレクションで標準の List を使用し、親コレクションにワイルドカード コレクション ID "-" を指定することをおすすめします。ライブラリ API の例では、次の REST API リクエストを使用できます。

GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx

サブコレクションからの一意のリソースの取得

サブコレクション内のリソースに、その親コレクション内で一意の識別子がある場合があります。この場合は、どの親コレクションに含まれているかを知らなくても、Get でそのリソースを取得できるようにしておくと便利です。そのような場合は、リソースで標準の Get を使用し、リソースが一意であるすべての親コレクションに対して、ワイルドカード コレクション ID "-" を指定することをおすすめします。たとえば、ライブラリ API では、すべての shelf のすべての book の中で一意である book について、次の REST API リクエストを使用できます。

GET https://library.googleapis.com/v1/shelves/-/books/{id}

この呼び出しに対するレスポンスのリソース名には、各親コレクションの "-" の代わりに、実際の親コレクション識別子を指定して、リソースの正規名を使用しなければなりません。たとえば、上記のリクエストでは、shelves/-/books/book8141 ではなく、shelves/shelf713/books/book8141 のような名前のリソースが返されます。

並べ替え順序

API メソッドでクライアントがリスト結果の並べ替え順序を指定できるようにする場合、リクエスト メッセージに次のフィールドを含めることが推奨されます。

string order_by = ...;

文字列の値は、SQL 構文に従って、フィールドのカンマ区切りリストにすることが推奨されます。例: "foo,bar"。デフォルトの並べ替え順序は昇順です。フィールドに対して降順を指定するには、フィールド名に接尾辞 " desc" を追加する必要があります。例: "foo desc,bar"

構文内の余分な空白文字は重要ではありません。 "foo,bar desc""  foo ,  bar  desc  " は同じです。

リクエストの検証

API メソッドに副作用があり、そのような副作用なくリクエストを検証する必要がある場合は、リクエスト メッセージに次のフィールドを含めることが推奨されます。

bool validate_only = ...;

このフィールドが true に設定されている場合、サーバーがいかなる副作用も実行できません。この場合、サーバーは完全なリクエストと一致する実装固有の検証のみを実行します。

検証が成功した場合、google.rpc.Code.OK が返される必要があり、同じリクエスト メッセージを使用する完全なリクエストは google.rpc.Code.INVALID_ARGUMENT を返すことはできませんgoogle.rpc.Code.ALREADY_EXISTS のような他のエラーや競合状態のため、リクエストが失敗する可能性があることに注意してください。

リクエストの重複

ネットワーク API では、ネットワーク障害の後に安全に再試行できるため、べき等の API メソッドが非常に好まれます。ただし、リソースの作成など、一部の API メソッドは簡単にべき等にすることはできません。不要な重複を避ける必要があります。このような使用例では、リクエスト メッセージに UUID のような一意の ID を含めることが推奨されます。サーバーはそれを使用して重複を検出し、リクエストが 1 回のみ処理されるようにします。

// A unique request ID for server to detect duplicated requests.
// This field **should** be named as `request_id`.
string request_id = ...;

重複したリクエストが検出された場合、クライアントは以前のレスポンスを受信していない可能性が高いため、サーバーでは以前に成功したリクエストに対するレスポンスを返すようにします

列挙型のデフォルト値

すべての列挙型の定義は 0 値のエントリで開始することが必須となります。列挙値が明示的に指定されていない場合は、このエントリを使用することが推奨されます。API で、0 値の処理方法を文書化することが必須となります。

列挙値 0ENUM_TYPE_UNSPECIFIED の名前にすることが必要です。一般的なデフォルト動作が存在する場合は、列挙値が明示的に指定されていない場合にデフォルト動作を使用する必要があります。一般的なデフォルト動作が存在しない場合は、0 値が使用された場合にエラー INVALID_ARGUMENT で拒否する必要があります

enum Isolation {
  // Not specified.
  ISOLATION_UNSPECIFIED = 0;
  // Reads from a snapshot. Collisions occur if all reads and writes cannot be
  // logically serialized with concurrent transactions.
  SERIALIZABLE = 1;
  // Reads from a snapshot. Collisions occur if concurrent transactions write
  // to the same rows.
  SNAPSHOT = 2;
  ...
}

// When unspecified, the server will use an isolation level of SNAPSHOT or
// better.
Isolation level = 1;

0 値には慣用名を使用することが可能です。たとえば、google.rpc.Code.OK はエラーコードがないことを指定する慣用的な方法です。この場合、列挙型のコンテキストで OKUNSPECIFIED と意味的に同等です。

本質的に理解可能で安全なデフォルトが存在する場合、その値を「0」値として使用できます。たとえば、BASICリソースビュー列挙の「0」値です。

文法の構文

API 設計では、多くの場合、受け入れ可能なテキスト入力など、特定のデータ形式に対して単純な文法を定義する必要があります。API 間で一貫したデベロッパー エクスペリエンスを提供し、容易に習得できるように、API 設計者は Extended Backus-Naur Form(EBNF)構文の次のバリアントを使用してそのような文法を定義する必要があります。

Production  = name "=" [ Expression ] ";" ;
Expression  = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term        = name | TOKEN | Group | Option | Repetition ;
Group       = "(" Expression ")" ;
Option      = "[" Expression "]" ;
Repetition  = "{" Expression "}" ;

整数型

API 設計では、uint32fixed32 などの符号なし整数型は、Java、JavaScript、OpenAPI などの重要なプログラミング言語やシステムではサポートされていないため、使用しないでください。オーバーフロー エラーが発生しやすくなります。もう 1 つの問題は、異なる API では、一致しない符号付きの型と符号なしの型が同じものに対して使用される可能性が非常に高いことです。

符号付きの整数型が、サイズやタイムアウトなど、負の値が意味を持たないものに使用されている場合は、値 -1-1 のみ)を使用して、ファイルの終わり(EOF)、無限のタイムアウト、無制限の割り当て上限、不明な存続期間など、特別な意味を示すことが可能です。そのような使用法は、混乱を避けるために明確にドキュメント化する必要があります。API プロデューサーは、明白でない場合は、暗黙的なデフォルト値 0 の動作もドキュメント化することが推奨されます。

部分レスポンス

API クライアントには、レスポンス メッセージ内のデータの特定のサブセットのみが必要な場合があります。このような使用例に対応するために、一部の API プラットフォームは部分レスポンスをネイティブにサポートしています。Google API プラットフォームはレスポンス フィールド マスクによってこれをサポートしています。

REST API 呼び出しの場合、暗黙的なシステムクエリ パラメータ $fields があります。これは、google.protobuf.FieldMask 値の JSON 表現です。レスポンス メッセージは、$fields によってフィルタリングされてから、クライアントに返送されます。このロジックは、API プラットフォームによってすべての API メソッドに対して自動的に処理されます。

GET https://library.googleapis.com/v1/shelves?$fields=shelves.name
GET https://library.googleapis.com/v1/shelves/123?$fields=name

リソースビュー

ネットワーク トラフィックを減らすには、サーバーがレスポンスで返すリソースの部分をクライアントが制限できるようにし、完全なリソース表現ではなくリソースのビューを返すことが役立つ場合があります。API でのリソースビューのサポートはメソッド リクエストにパラメータを追加することで実装され、これによりクライアントはレスポンスで受信するリソースのビューを指定できます。

パラメータの要件は次のとおりです。

  • enum 型である必要があります。
  • view という名前にすることが必須です。

列挙の個々の値では、リソースのどの部分(どのフィールド)がサーバーのレスポンスで返されるかを定義します。各 view 値に対して返される正確な内容は実装で定義し、API ドキュメントで指定する必要があります。

package google.example.library.v1;

service Library {
  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
    option (google.api.http) = {
      get: "/v1/{name=shelves/*}/books"
    }
  };
}

enum BookView {
  // Not specified, equivalent to BASIC.
  BOOK_VIEW_UNSPECIFIED = 0;

  // Server responses only include author, title, ISBN and unique book ID.
  // The default value.
  BASIC = 1;

  // Full representation of the book is returned in server responses,
  // including contents of the book.
  FULL = 2;
}

message ListBooksRequest {
  string name = 1;

  // Specifies which parts of the book resource should be returned
  // in the response.
  BookView view = 2;
}

この構成は、次のような URL にマップされます。

GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC

メソッド、リクエスト、レスポンスの定義について詳しくは、この設計ガイドの標準メソッドの章をご覧ください。

ETag

ETag は、クライアントが条件付きリクエストを行うことを可能にする不透明な識別子です。 ETag をサポートするには、API のリソース定義に文字列フィールド etag を含めることが必要で、そのセマンティクスは ETag の一般的な使用方法と一致していなければなりません。通常、etag にはサーバーによって計算されたリソースのフィンガープリントが含まれます。詳細については、WikipediaRFC 7232 をご覧ください。

ETag に対しては、強い検証と弱い検証が可能です。弱い検証の ETag の前に W/ が付けられます。このコンテキストで、強い検証とは、同じ ETag を持つ 2 つのリソースがバイト単位で同一のコンテンツと同一の追加フィールド(つまり Content-Type)の両方を持つことを意味します。これは、強い検証の ETag では部分レスポンスのキャッシュを後でアセンブルできることを意味します。

逆に、弱い検証の同じ ETag 値を持つリソースは、表現は意味的に同等でも、必ずしもバイト単位で同一ではないため、バイト範囲リクエストのレスポンス キャッシュには適していないことを意味します。

例:

// This is a strong ETag, including the quotes.
"1a2f3e4d5b6c7c"
// This is a weak ETag, including the prefix and quotes.
W/"1a2b3c4d5ef"

引用符は実際に ETag 値の一部であり、RFC 7232 に準拠するために存在する必要があることを理解することが重要です。つまり、ETag の JSON 表現では引用符をエスケープすることになります。たとえば、JSON リソースの本文で、ETag は次のように表現されます。

// Strong
{ "etag": "\"1a2f3e4d5b6c7c\"", "name": "...", ... }
// Weak
{ "etag": "W/\"1a2b3c4d5ef\"", "name": "...", ... }

ETag で使用可能な文字の要約は次のとおりです。

  • 印刷可能な ASCII のみ。
    • RFC 2732 で許可されている非 ASCII 文字。ただし、デベロッパー向けではありません。
  • スペースは使用できません。
  • 上記の位置以外の二重引用符は使用できません。
  • RFC 7232 で推奨されているように、エスケープでの混乱を防ぐために、バックスラッシュを避けます。

出力フィールド

API は、入力としてクライアントによって提供されるフィールドと、特定のリソースの出力時にサーバーによって返されるフィールドを区別する場合があります。出力専用フィールドについては、フィールド属性にアノテーションを付加する必要があります

出力専用フィールドがリクエストに設定された場合、または google.protobuf.FieldMask に含まれている場合、サーバーはエラーなしでリクエストを受け入れる必要があります。サーバーは出力専用フィールドの存在とその存在を示すものを無視することが必須となります。これが推奨される理由は、クライアントがサーバーから返されたリソースを別のリクエスト入力として再利用することが多いためです。たとえば、取得した Book は、後で UPDATE メソッドで再利用されます。出力専用フィールドが検証された場合、出力専用フィールドをクリアするための余分な作業がクライアントで必要になります。

import "google/api/field_behavior.proto";

message Book {
  string name = 1;
  Timestamp create_time = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
}

シングルトン リソース

シングルトン リソースは、リソースの単一のインスタンスのみが親リソース内(または親を持たない場合は API 内)に存在する場合に使用できます。

シングルトン リソースは、標準の CreateDelete メソッドが省略されることが必須です。シングルトンは、親が作成または削除されると、暗黙的に作成または削除されます(親がない場合は暗黙的に存在します)。リソースには、標準の Get メソッドや Update メソッドのほか、ユースケースに適したカスタム メソッドを使用してアクセスしなければなりません

たとえば、User リソースを持つ API は Settings シングルトンとしてユーザーごとの設定を公開する可能性があります。

rpc GetSettings(GetSettingsRequest) returns (Settings) {
  option (google.api.http) = {
    get: "/v1/{name=users/*/settings}"
  };
}

rpc UpdateSettings(UpdateSettingsRequest) returns (Settings) {
  option (google.api.http) = {
    patch: "/v1/{settings.name=users/*/settings}"
    body: "settings"
  };
}

[...]

message Settings {
  string name = 1;
  // Settings fields omitted.
}

message GetSettingsRequest {
  string name = 1;
}

message UpdateSettingsRequest {
  Settings settings = 1;
  // Field mask to support partial updates.
  FieldMask update_mask = 2;
}

ストリーミング ハーフクローズ

すべての双方向またはクライアント ストリーミング API では、サーバーは、クライアント側ストリームの完了には、RPC システムにより提供される、クライアントが開始したハーフクローズに依存することが推奨されます。明示的な完了メッセージを定義する必要はありません。

ハーフクローズの前にクライアントが送信する必要があるすべての情報は、リクエスト メッセージの一部として定義する必要があります。

ドメインをスコープとする名前

ドメインをスコープとする名前は、名前の競合を防ぐために DNS ドメイン名が接頭辞に付いたエンティティ名です。さまざまな組織の間でエンティティ名の定義方法が統一されていない場合、この設計パターンが有効です。この構文は、スキーマのない URI と似ています。

ドメインをスコープとする名前は、次のような Google API や Kubernetes API で広く利用されています。

  • Protobuf Any 型表現: type.googleapis.com/google.protobuf.Any
  • Stackdriver 指標タイプ: compute.googleapis.com/instance/cpu/utilization
  • ラベルキー: cloud.googleapis.com/location
  • Kubernetes API のバージョン: networking.k8s.io/v1
  • x-kubernetes-group-version-kind OpenAPI 拡張機能の kind フィールド

ブール型、列挙型、文字列型

API メソッドを設計する場合、トレースの有効化やキャッシュの無効化など、特定の機能のセットを提供するのが一般的です。一般的な方法としては、boolenumstring タイプのいずれかのリクエスト フィールドを導入します。特定のユースケースに適したタイプがすぐにわかるとは限りません。おすすめの方法は次のとおりです。

  • 固定デザインで、意図的に機能を拡張したくない場合は、bool 型を使用します。例: bool enable_tracing または bool enable_pretty_print

  • 柔軟なデザインにしたいが、デザインが頻繁に変更されないようにしたい場合は、enum 型を使用します。経験則として、列挙型の定義は年に 1 回かそれ以下しか変更されません。例: enum TlsVersion または enum HttpVersion

  • オープンエンド デザインの場合や、外部標準によって頻繁に変更される可能性がある場合は、string 型を使用します。サポートされている値は、明確にドキュメント化する必要があります。例:

データ保持

API サービスを設計する場合、データの保持はサービスの信頼性に不可欠な要素となります。ソフトウェアのバグや人的ミスによって、ユーザーデータが誤って削除されることがよくあります。データ保持と対応する削除取り消し機能がないと、単純なミスがビジネスに壊滅的な影響を与える可能性があります。

一般に、API サービスには次のデータ保持ポリシーをおすすめします。

  • ユーザーのメタデータ、ユーザー設定などの重要な情報には、30 日間のデータ保持が必要です。たとえば、モニタリング指標やプロジェクト メタデータ、サービス定義などです。

  • 大量のユーザー コンテンツについては、7 日間のデータ保持期間が必要です。 たとえば、バイナリ blob やデータベース テーブルなどです。

  • 一時的な状態または高価なストレージの場合、可能であれば 1 日間のデータ保持が必要です。たとえば、Memcache インスタンスや Redis サーバーなどです。

データ保持期間中は、データを失うことなくデータ削除の取り消しができます。 無料でのデータ保持の提供は費用がかかる場合は、有料サービスとしてデータ保持を提供することもできます。

大きなペイロード

ネットワーク API は多くの場合、データパスの複数のネットワーク レイヤに依存します。ほとんどのネットワーク層には、リクエストとレスポンスのサイズにハードリミットがあります。多くのシステムでは一般的に 32MB がリミットになっています。

10 MB を超えるペイロードを処理する API メソッドを設計する場合、ユーザビリティと今後の成長に適した戦略を慎重に選択する必要があります。Google API では、大きなペイロードを処理するために、ストリーミングまたはメディアのアップロード / ダウンロードを使用することをおすすめします。ストリーミングを使用すると、サーバーが Cloud Spanner API などの大量の同期データを段階的に処理します。メディアを使用すると、大量のデータは Google Cloud Storage などの大容量のストレージ システムを介して流れ、サーバーは Google Drive API などの非同期データを処理できます。

オプションのプリミティブ フィールド

プロトコル バッファ v3(proto3)は、optional プリミティブ フィールドをサポートしています。プリミティブ フィールドは、多くのプログラミング言語の nullable 型と意味的に同等です。プリミティブ フィールドは、空の値と設定されていない値を区別するのに使用できます。

実際には、デベロッパーが省略可能なフィールドを正しく処理することは困難です。Google API クライアント ライブラリを含め、ほとんどの JSON HTTP クライアント ライブラリは、proto3 int32google.protobuf.Int32Valueoptional int32 を区別できません。代替の設計が明確性の点で同等であり、オプションのプリミティブが必要ない場合は代替設計を使用します。オプションを使用しない場合は、複雑性または曖昧性が増大するため、その場合はオプションのプリミティブ フィールドを使用します。今後は、ラッパータイプを使用しないでください。一般に、API 設計者は、一貫性と整合性を確保するため、int32 などのプレーン プリミティブ型を使用することを必要とします