はじめに
Zalando RESTful API と イベントスキーマのガイドラインより学んだことをまとめる.
REST API設計の原則やOpenAPIの仕様記述等は省略する.
リソース
アクションはHTTPメソッドで実現
- RESTはリソースにまつわるものに関心がある
- Webサービスとドメインエンティティが行うやり取りはHTTPメソッドで実現する
- 例
- 記事を編集するアプリケーションで,同時に1人のユーザしか編集できないよう明示的にロックしたいケースを考える
- この際は「ロックする」というアクションの代わりに「記事のロック」をPUTまたはPOSTで生成する
PUT /article-locks/{article-id}
ドメイン固有のリソース名をつける
- 開発者がAPIの意味を理解しやすくすべき
- ドメインをそのまま設計に落とし込めるのがベスト
- 例
- 「sales-order-items」は「order-items」や「items」よりもドメインを明確に指し示している
パスセグメントによりリソースとサブリソースを識別する
- 直感的に理解できるURLにするため指定する順番を統一する
/{resources}/[resource-id]/{sub-resources}/[sub-resource-id]
- 例
- ユーザのカート内商品を取得するAPI
/carts/{cart-id}/items/{item-id}
/carts/1681e6b88ec1/items/1
リソースごとのAPIを増やしすぎない
- 単一のAPIに異なる業務機能群を混ぜ込まないようにする
- 例
-
/customers /customers/{id} /customers/{id}/preferences → ❌ /customers/{id}/addresses /customers/{id}/addresses/{addr} /addresses /addresses/{addr}
- 悪い例
/customers/{id}/preferences
- これは顧客の住んでいる都道府県を取得していて一見良さそうに見えるが,customersとpreferencesは1対1なので
/customers/{id}
に含めるべき
- このように,それらが同一のものであると確信もって言えるすべがない場合は分割してOK!
- ただ,分割の目安は1つのリソースに4~8個らしいのでそれ以上になると工夫が必要
UUIDを多用しない
-
一意のIDを割り振れるため多用しがちだが,デメリットも存在する
- 人工的なキーなのでそれ自体に意味を持たない
例えば,製品属性に付けられた名前を使うなど代替案を考えるべき - 使いづらい
- 人間には覚えられないし,それを使ってコミュニケーションできない
- デバッグやログ解析に使いづらい
- かなり長い: 読めるキャラクタ形式にすると36文字にもなり,メモリや帯域の圧迫の原因となる
- 生成順に並べることができない
- レガシーなIDの後方互換サポートと競合するかもしれない
- 人工的なキーなのでそれ自体に意味を持たない
-
UUIDはID生成がボトルネックとなるようなときまで避けるべき
じゃあどうするの?
- サーバーで一意なIDが必要だが順番は不要
- UUID v4を使用
- ソートやページネーションが必要な場合
- UUID v7 or ULID(Universally Unique Lexicographically Sortable Identifier)を使う
- IDをクライアントが生成したい(非同期)
- UUID/ULIDは有効
- 業務的に人が確認するもの
- 意味あるラベル or カスタムキーの方が好ましい
- 高速・高負荷なAPIでID生成がボトルネック
- UUIDのような非協調な分散ID生成を検討(ただし本当に必要なときだけ)
注意点
- 連番識別子は,機密情報の権限をもたない顧客にまで漏らしてしまう可能性がある
- どんな場合でもIDには文字列型を使うべき!
- API仕様にはstringとして表記し,uuidの使用を公開しない
HTTPリクエスト
GET
- 基本的に,GETのボディはつけない
- クエリパラメータ等で指定すべき
- ただ,多くのクエリパラメータをつけてしまうとクライアントやロードバランサ,サーバのサイズ制限を超える可能性がある
- また,URIに含めるため第三者が閲覧できてしまう可能性がある
- GETの役割をしたPOST
- POSTのボディを使用することで通信を行う方法
- この場合は
GET with Body
というヒントをドキュメント等で共有しておく必要がある
PATCH
- 後方互換性(フィールドの削除等)を壊しやすく,開発者に誤解されやすいため,対応を推奨順に記述
- リソースの更新にはオブジェクトまるごと全体を渡すPUTを使う
- リソースの一部を更新するためだけに、部分的なオブジェクトでPATCHを使う
- JSON Patch で規定されたPATCHを使う
- PATCHの代わりに (何が起きたかの正しい記述がされた) POSTを使う
- 基本的にPUTで更新を行う方が良さそう
冪等性
- RFC 7231より引用
+---------+------+------------+---------------+
| Method | Safe | Idempotent | Reference |
+---------+------+------------+---------------+
| CONNECT | no | no | Section 4.3.6 |
| DELETE | no | yes | Section 4.3.5 |
| GET | yes | yes | Section 4.3.1 |
| HEAD | yes | yes | Section 4.3.2 |
| OPTIONS | yes | yes | Section 4.3.7 |
| POST | no | no | Section 4.3.3 |
| PUT | no | yes | Section 4.3.4 |
| TRACE | yes | yes | Section 4.3.8 |
+---------+------+------------+---------------+
POSTの冪等性
- noとなっているが冪等にするのが望ましい
- 困る例
- 通信失敗でリトライしたら、同じ注文が2回通った(ゾンビリソース)
- 複数の更新が競合して、どちらかが上書きされた(ロストアップデート)
- ロードバランサやクライアントが勝手にリトライする可能性がある
冪等性を持たせるためには
An idempotency key is a unique value generated by the client which the resource uses to recognize subsequent retries of the same request.
参考:The Idempotency-Key HTTP Header Field
リソースを生成したり更新したりするとき、タイムアウトやネットワーク障害のためにリトライするケースで、重複実行を避けるため同じレスポンスを返す強い冪等性が役に立ったり、必要になったりします。一般的にこれはクライアント固有の 一意のリクエストキー をリソースの一部ではなく、Idempotency-Keyヘッダを通じて送信することによって実現されます。
参考:「Zalando RESTful API と イベントスキーマのガイドライン」
-
Idempotency-Key
を管理することで冪等性を持たせることができる - クライアントがUUID等の一意な文字列を生成してリクエストに含める
- サーバはこのキーに基づいて処理済かどうかを判定し同じ処理を繰り返さないようにする
- リクエストごとに
Idempotency-Key
を保存しておくとデータ量が爆発するので,有効期限やバッチ処理等の工夫が必要
処理責務
- 処理済かつ同一ペイロードの場合はキャッシュされた情報を返す
- 処理中かつ同一ペイロードの場合は409エラーを返す
- 同一キーかつ異なるペイロードの場合は422エラーを返す
性能
通信帯域幅を減らす
- クライアントの必要性に応じて必要ないデータを削減する仕組みを実装すべき
- (大きなペイロードや高トラフィックなシナリオなら大事)
- モバイルWebアプリのクライアントは特に低帯域での通信が必要になる
テクニック
- 巨大なデータコレクションに対してページネーションを適用
- リクエストとレスポンスのボディをgzipで圧縮
- フィールドフィルタをクエリに実装
- (これするくらいならGraphQLでもいいのではと思った)
- ETagとIf-Match/If-None-Matchヘッダを使って、変更のないリソースの再フェッチを避ける
- これに関しては次節にまとめる
競合検出や整合性の維持
-
ETag
,If-Match/If-None-Match
を利用して更新の競合や重複作成を防止する
用語
-
ETag(Entry-Tags)
-
ETag
はリソースのバージョンを識別するためのハッシュ値 - リソースの変更ごとに異なる値が生成される
-
-
If-Match
- ETagのバージョンと一致するなら更新していいよという値
-
If-None-Match
- リソースがまだ存在しなければ処理を実行していいよという値
-
リソースのETagを管理しておき,逐次検証することで実現できる
セキュリティ
OAuth2.0でエンドポイントをセキュアにする
- Basic認証,API Key,JWT,プロバイダ認証,OpenID Connect等がある
- 基本的にはJWTで認可するのが良さそう