一般的な設計パターン

空のレスポンス

標準の 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 はリソースラベル設計パターンを使用することが推奨されます。リソースラベル設計パターンは google.api.LabelDescriptor で記述します。

これを行うには、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 name = 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 フィールドを使用して項目の総数を指定することも可能です。

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

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

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

注: "*" ではなく "-" を選択する理由は、URL エスケープの必要をなくすためです。

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

サブコレクション内のリソースに、その親コレクション内で一意の識別子がある場合があります。この場合、どの親コレクションに含まれているかを知らなくても 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 を使用することが推奨されます。また、予測される動作を API で文書化することが推奨されます。

一般的なデフォルト動作がない場合、列挙値 0ENUM_TYPE_UNSPECIFIED という名前にすることが推奨されます。また、これが使用された場合はエラー 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 "}" ;

注: TOKEN は、文法の外部で定義された終端記号を表します。

整数型

uint32fixed32 などの符号なしの整数型は、Java、JavaScript、OpenAPI などの一部の重要なプログラミング言語やシステムでサポートされていないため、API 設計では使用しないことが推奨されます。これらの整数型を使用すると、オーバーフローのエラーが発生する可能性が高くなります。もう 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=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 メソッドで再利用されます。出力専用フィールドが検証された場合、出力専用フィールドをクリアするための余分な作業がクライアントで必要になります。

message Book {
  string name = 1;
  // Output only.
  Timestamp create_time = 2;
}

シングルトン リソース

シングルトン リソースは、リソースの単一のインスタンスのみが親リソース内(または親を持たない場合は 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.Duration
  • Stackdriver 指標タイプ: compute.googleapis.com/instance/cpu/utilization
  • ラベルキー: cloud.googleapis.com/location
  • Kubernetes API のバージョン: networking.k8s.io/v1
  • x-kubernetes-group-version-kind OpenAPI の拡張内の kind フィールド
このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...