API Design
はじめに
改めて自分の中でAPI設計について整理するために、Googleが公開しているAPI設計ガイドを読みました。
cloud.google.com
基本的なリソースやパス設計の考え方はRails Wayに馴染みのあるものですが、一部自分の新たな気付きになったものを中心にピックアップして、自分用にまとめました。
なお、この記事内の記載している例については、ガイド内で紹介されている内容を引用しています。
また、RESTに限らずgRPCなどのケースであっても適用可能なガイドになっています。
用語
コレクション
リソースに対し、コレクションは同じタイプのサブリソースのリストを含む特殊なリソース。
標準メソッド
GoogleのAPI設計では、リソースに対する操作は基本的に、List/Get/Create/Update/Delete を提供します。
多数のリソース+少数のメソッドでAPI設計をするのがベターとされています。
カスタムメソッド
標準メソッドだけでは表現が困難な操作があります。
例えば、「削除操作の取り消し」や「インスタンスの再起動」などがあります。
標準メソッドの動詞(List|Get|Create|Update|Delete)だけでは表現できない操作を対象に定義されます。
API設計について
標準メソッドの場合
List
List を使用する場合は、コレクション名+0個以上のパラメータを使用します。
また、List操作はHTTP GETメソッドを使用する必要があり、コレクション名はパスにマッピングする(本文に含めない)ことが推奨されます。
例)
- GET
shelves
(コレクション名:shelves
) - GET
shelves/shelf1/books
(コレクション名:books
)
Listを使用するケースとしては、検索が代表的です。
ただし、簡易的な検索(フィルタリングなど)ではなく、範囲の広い検索においては、カスタムメソッドsearch
を使用することが推奨されます。
また、レスポンス本文には任意のメタデータを含めることが推奨されます。
例えば、ページングを行うための page_size
や next_token
を含めます。
Get
Get を使用する場合は、リソース名+0個以上のパラメータを使用します。
また、HTTP GETメソッドを使用する必要がありリソース名はパスにマッピングすることが推奨されます。
例)
- GET
shelves/shelf1
(リソース名:shelf1
) - GET
shelves/shelf1/books/book2
(リソース名:book2
)
返却されるリソースは本文全体にマッピングされます。
=> Getのレスポンス専用の型を用意してならず、リソース全体を本文として返します。
CreateやUpdateでも同様です。
Create
Create を使用する場合は、親リソース名+リソース+0個以上のパラメータを使用します。
また、HTTP POSTメソッドを使用する必要があり、リクエストメッセージには親リソースID parent
を含めることが推奨されます。
例)
{ "parent": "shelves/shelf1", "book": { "name": "book name" } }
リソースに関連しないその他リクエストメッセージは、すべてクエリパラメータにマッピングされるべきです。
Getの場合と同様、作成したリソースは、レスポンス本文全体にマッピングされるべきです。
Update
Update を使用する場合は、リソース名+0個以上のパラメータを使用します。
リソース名や親リソース名を除き、変更可能なリソースのプロパティは基本的に更新可能にする必要があります。
また、リソース名の変更や親の移動を行う場合は、カスタムメソッドを使用することが推奨されます。
レスポンスに関しては、更新されたリソース自体であることが必須となります。
また、部分的な更新を行えるように、FiledMaskでPATCHを使用する必要もあります。
例)
PATCH shelves/shelf1/books/book2
{ "book": { "name": "updated book name" }, "update_mask": "book.name" }
リソースに関連しない残りのメッセージフィールドは、すべてクエリパラメータにマッピングされるべきです。
Delete
Delete を使用する場合は、リソース名+0個以上のパラメータを使用します。
また、HTTP DELETEメソッドを使用する必要があり、リソース名はパスにマッピングすることが推奨されます。
残りのメッセージフィールドは、すべてクエリパラメータにマッピングされるべきです。
例) DELETE shelves/shelf1/books/book2
Delete は、指定したリソースを即時削除するか、削除をスケジュールします。
即時削除の場合、google.protobuf.Empty
(空の本文)を返すことが推奨される。
削除済みとしてマークするだけの場合、更新されたリソースを返すことが推奨される。
カスタムメソッドの場合
カスタムメソッドを使用する場合、基本的には柔軟性を持つ(bodyを使用できる) HTTP POST メソッドが推奨されます。
一方、PATCH の使用は推奨されません。
また、カスタムメソッドが関連するリソースやコレクションのリソース名は、パスにマッピングされることが推奨されます。
カスタムメソッドが有効な代表的なケースは以下のように紹介されています。
メソッド名 | 動詞 | HTTP Method |
---|---|---|
キャンセル | :cancel | POST |
batchGet | :batchGet | GET |
移動 | :move | POST |
検索 | :search | GET |
削除の取り消し | :undelete | POST |
(削除の取り消しについて、削除済みのリソースの推奨保持期間は30日。)
GETを使用するケースでは、冪等であり副作用がないことが前提となります。
カスタム動詞を使用する場合は、動詞とリソース名を /
ではなく :
で区切ります(/
を含まないことで、任意のパスで対応できるため)。
例) shelves/shelf1:undelete
カスタム動詞は接尾辞として付与することが必須です。
命名について
リソースID
リソース指向APIでは、リソースは名前付きエンティティであり、リソース名はその識別子となります。
各リソースには、一意性を表すためのリソース名が必要であり、以下で構成されます。
- リソース自体のID
- すべての親リソースのID
- そのAPIサービス名
例)
"//library.googleapis.com/shelves/shelf1/books/book2"
(完全なリソース名)"shelves/shelf1/books/book2"
(相対的なリソース名)
コレクションIDの設計
コレクション名の設計については、以下を満たすように設計します。
- 簡素で明確な英単語を使用する
- キャメルケースで使用する
- コレクションIDでは、非常に一般的な用語は使用しないか、修飾して使用する(values -> rowValues)
以下の単語は、修飾して使用することが推奨されます。
- elements
- entries
- instances
- items
- objects
- resources
- types
- values
リソース定義
リソースを定義する場合、最初のフィールドはリソース名の文字列フィールドにする必要があり、フィールド名にはname
という用語を用います。
これは、name
が非常に広い意味を持つ用語であり、開発者によって解釈が異なる可能性が高いため、予約語として使用します(その他のフィールド名では、title
などより明確な用語を使用することが強いられる)。
一般的な設計パターン
空レスポンス
Deleteでは、ソフト削除を除いて基本的にgoogle.protobuf.Empty
(空の本文)を返す必要がある。
一方、ソフト削除の場合は状態の変更がわかるように、更新後のリソースを返す必要がある。
区間表現
半開区間を使用する必要がある。
また、命名には start_xxx
や end_xxx
といった規則を用いる。
例) [start_date, end_date)
/ [start_time, end_time)
型は Timestamp
長時間実行オペレーション
実行に長時間かかるかかるAPIメソッドは、長時間実行オペレーションのリソースをクライアントに返すように設計できます。
Operation
インターフェースを使用し、長時間実行APIごとにインターフェースが独自に定義されないようにする必要があります。
サブコレクションのList
shelfとサブコレクションであるbookに対し、すべてのshelfに属するbookを検索する(親のshelfに対する制約がない)場合、以下のようなREST APIを使用します。
例) GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx
親コレクションに対してワイルドカードとして-
を使用します。
サブコレクションからの一意なリソース取得
取得したいbookリソースが、親であるshelfリソースのIDを知らなくても取得できるようにしておくと便利です。
その場合、以下のようなREST APIを使用できます。
例) GET https://library.googleapis.com/v1/shelves/-/books/{id}
サブコレクションのListと同様、ワイルドカードとして-
を使用します。
並び替え順序
リクエストに order_by
を含め、SQL構文に従いカンマ区切りで指定します。
foo, bar desc
リクエストの重複
基本的に冪等なAPIが好まれますが、簡単には冪等にできないケースもあります(リソースの作成やデータベーストランザクションなど)。
このような場合、リクエストにUUIDなど一意な値を持たせることが推奨されます。
サーバーはその値を用いて重複を検知し、リクエストが1度だけ実行されるようにします。
列挙型のデフォルト値
列挙型エントリは0
から開始することが必須となります。
明示的に指定されていない場合、このエントリが使用されることが推奨されます。
一般的なデフォルト動作がない場合、列挙型の0
はENUM_TYPE_UNSPECIFIED
という名前にすることが推奨されます。
また、これが使用された場合は、INVALID_ARGUMENT
で拒否することが推奨されます。
部分レスポンス
レスポンスメッセージ内の特定のサブセットのみが必要なケースでは、暗黙的なシステムクエリパラメータ$field
を使用します。
例) GET https://library.googleapis.com/v1/shelves?$fields=name
これは、FieldMaskのJSON表現として使用できます。
シングルトンリソース
例えば、ユーザーリソースに対してユーザー固有の設定はシングルトンリソースとして存在します。
シングルトンリソースは、標準のCreate/Deleteが省略されることが必須です。
親が作成または削除されると、暗黙的に作成、削除されます。
リソースにアクセスするためには、Get/Updateかカスタムメソッドを用いてアクセスしなければなりません。
データ保持
誤ってデータを削除してしまうことは多々あり、ビジネス的ダメージを軽減するため、データの削除ポリシーとして一定期間データを保持することが推奨されます。
データ保持の推奨期間は以下のように記載されています。