空のレスポンス
標準の 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_size
が0
の場合、サーバーは返される結果の数を決定します。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
値の処理方法を文書化することが必須となります。
列挙値 0
は ENUM_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
はエラーコードがないことを指定する慣用的な方法です。この場合、列挙型のコンテキストで OK
は UNSPECIFIED
と意味的に同等です。
本質的に理解可能で安全なデフォルトが存在する場合、その値を「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 設計では、uint32
や fixed32
などの符号なし整数型は、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
メソッド、リクエスト、レスポンスの定義について詳しくは、この設計ガイドの標準メソッドの章をご覧ください。
ETags
ETag は、クライアントが条件付きリクエストを行うことを可能にする不透明な識別子です。
ETag をサポートするには、API のリソース定義に文字列フィールド etag
を含めることが必要で、そのセマンティクスは ETag の一般的な使用方法と一致していなければなりません。通常、etag
にはサーバーによって計算されたリソースのフィンガープリントが含まれます。詳細については、Wikipedia と RFC 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 7232 で許可されている非 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 内)に存在する場合に使用できます。
シングルトン リソースは、標準の Create
と Delete
メソッドが省略されることが必須です。シングルトンは、親が作成または削除されると、暗黙的に作成または削除されます(親がない場合は暗黙的に存在します)。リソースには、標準の 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 メソッドを設計する場合、トレースの有効化やキャッシュの無効化など、特定の機能のセットを提供するのが一般的です。一般的な方法としては、bool
、enum
、string
タイプのいずれかのリクエスト フィールドを導入します。特定のユースケースに適したタイプがすぐにわかるとは限りません。おすすめの方法は次のとおりです。
固定デザインで、意図的に機能を拡張したくない場合は、
bool
型を使用します。例:bool enable_tracing
またはbool enable_pretty_print
。柔軟なデザインにしたいが、デザインが頻繁に変更されないようにしたい場合は、
enum
型を使用します。経験則として、列挙型の定義は年に 1 回かそれ以下しか変更されません。例:enum TlsVersion
またはenum HttpVersion
。オープンエンド デザインの場合や、外部標準によって頻繁に変更される可能性がある場合は、
string
型を使用します。サポートされている値は、明確にドキュメント化する必要があります。次に例を示します。- Unicode リージョンで定義される
string region_code
- Unicode ロケールで定義される
string language_code
- Unicode リージョンで定義される
データ保持
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 の int32
、google.protobuf.Int32Value
、optional int32
を区別できません。別の設計が同様に明確で、オプションのプリミティブが不要な場合は、そのようにおすすめします。オプションを使用しない場合、複雑さや不明確さが増す場合は、プリミティブ フィールドを使用します。今後、ラッパー型は使用できません。一般に、API 設計者には、簡潔さと整合性を確保するために、int32
などのプレーンなプリミティブ型を使用することが推奨されます。