仕組みから学ぶ Web

API 設計

他人が使う契約をつくる

API は一度公開すると簡単には変えられない契約になる。リソースの切り方、同じリクエストが2回届いたときの振る舞い、失敗の伝え方。これらは実装の都合ではなく、呼ぶ側が安全なコードを書けるかどうかで決まる。

読み終わると、これに答えられるようになります

  • ?決済 API に同じリクエストが2回届いたらどうなるべきか
  • ?エラーレスポンスに何を入れると呼び出し側が自動復旧できるのか
  • ?GraphQL にすると何が楽になり、何が難しくなるのか
応用14分

リソース設計 — 一度公開したら変えられない契約

URL は動詞ではなく名詞で切る、の理由。そして「きれいな REST」に収まらない操作をどう扱うか。

レスポンスの1フィールドを削除したら、把握していなかった3つのクライアントが壊れた

応用14分

冪等性 — 同じリクエストが2回届く前提で設計する

ネットワークがある限り、再送は異常ではなく正常系。2回実行されても壊れない形をどう作るか。

決済 API のタイムアウト後にリトライし、同じ注文で2回課金された

応用13分

エラー設計 — 呼び出し側が正しく振る舞える情報を返す

エラーは失敗の通知ではなく、次に何をすべきかの指示。リトライしてよいか、直せば通るかを伝える。

すべてのエラーを 200 + { success: false } で返し、クライアントが一時障害を延々とリトライした

発展15分

REST と GraphQL — 何を楽にして、何を難しくするか

過不足のない取得という利点の裏で、キャッシュ・認可・負荷制御がクライアント任せから自分の責任に移る。

GraphQL 導入後、CDN キャッシュが一切効かなくなり、オリジンの負荷が10倍になった

応用14分

ページネーション — 数えて飛ばすか、続きから取るか

offset は「ページ番号」という画面の都合から来ている。件数が増えると遅くなり、データが動くと境界がずれる。カーソル方式が何を解いて、何を捨てるのか。

offset で一覧を書き出すバッチが、毎回数件ずつ静かに取りこぼしていた

応用15分

リトライとタイムアウト — 助けるつもりの再試行が、障害を大きくする

決済サービスは2分で直ったのに、全体は40分止まった。層ごとのリトライは掛け算になり、揃っていないタイムアウトは誰も待っていない仕事を増やす。

決済サービスが2分遅くなっただけで、3層のリトライが呼び出しを27倍にし、全体が40分止まった

応用15分

非同期処理とキュー — 「受け付けました」のあとで、何が起きているか

メール送信をキューに移したら、同じメールが4通届く人と、1通も届かない人が出た。キューは「必ず1回」を約束しない。重複と消失は、処理を書く側が引き受ける。

メール送信をキューに移したら、注文確認メールが4通届く人と、1通も届かない人が出た

応用14分

レート制限 — 誰を、どの単位で、どこで止めるか

ログインに IP ごとの制限をかけたら、同じ会社の社員がまとめて締め出され、数千の IP に散らばった攻撃は素通りした。制限の強さより先に、何を1人と数えるかを決める。

IP ごとのログイン制限で、同じ会社の社員がまとめて締め出され、分散した攻撃は素通りした

応用15分

API のバージョニング — 変えられないものを、どう変えるか

レスポンスから使っていないフィールドを1つ消した。翌日、古いアプリが全部落ちた。自分が使っていないことと、誰も使っていないことは違う。

使っていないフィールドを消したら、更新していない利用者のアプリが全部落ちた

応用15分

ファイルのアップロード — サーバーを通すか、通さないか

動画の投稿を許したら、アプリのメモリが枯れて全機能が落ちた。1人が 800MB を送っただけだった。受け取り方を決める前に、どこを通すかを決める。

動画の投稿を許した日、1人が 800MB を送ってアプリ全体がメモリ不足で落ちた

発展16分

2 つのシステムに書く — 片方だけ成功した状態を、先に設計しておく

決済は成功した。自社の DB には「未払い」と残った。利用者はお金を払ったのに使えなかった。自社の DB と外部のサービスは、1 つのトランザクションにならない。だから、途中で止まったときにどちらへ倒れるかを、先に決めておく。

決済は成功したのに、直後のデプロイで処理が止まり、自社の DB には「未払い」が残った。37 人が、払ったのに使えなかった