9 分で読了 #networking #HTTP
このページの目次

RESTとAPI設計

RESTは仕様ではなく、一連のアーキテクチャ制約(HATEOAS、ステートレス、統一インターフェース)です。GraphQLとgRPCは、それぞれ異なる制約の次元でRESTに対するトレードオフを行っています——どちらを選ぶかは、「レスポンスの形状を誰が決定するか」と「パフォーマンスへの感度」にかかっています。

概要

RESTは2000年にRoy Fieldingの博士論文で提唱されました——それはプロトコルではなくアーキテクチャスタイルであり、システムの状態を「リソース」としてモデル化し、HTTPの既存のメソッドセマンティクス(GET/POST/PUT/DELETE)とステータスコードを使用して操作することを提唱しています。独自のカスタムRPCを新たに発明するのではなくです。20年以上が経過した現在も、RESTはWeb APIのデファクトスタンダードであり続けています(GitHub/Stripe/Twilioなど)。一方、GraphQL(Facebook 2015)とgRPC(Google 2016)は、「クライアントのフィールド要件が多様である場合」や「内部向けの高パフォーマンスRPC」という、RESTが苦手とするシナリオを補完しています。優れたAPIの難しさは、どのスタイルを選ぶかではなく、⁠一貫性にあります:予測可能なURL、正しいメソッド/ステータスコードのセマンティクス、統一されたエラー処理とページネーション、明確なバージョン管理と互換性ポリシーです。

RESTの6つの制約

1. Client-Server     : UIとデータを分離し、それぞれ独立して進化させる
2. Stateless         : 各リクエストにセッションコンテキストがすべて含まれる → サーバーはセッションを保持しない → 水平スケーリングが容易
3. Cacheable         : レスポンスのキャッシュ可能性を明示的に宣言する (Cache-Control / ETag)
4. Uniform Interface : 統一されたリソース識別子と操作セマンティクス (以下のメソッド表を参照)
5. Layered System    : クライアントはバックエンドの階層構造を認識しない (CDN / LB / APIゲートウェイ)
6. Code on Demand    : (オプション) サーバーはクライアントを拡張するための実行可能コードを提供できる

Stateless(ステートレス)は、現実的な影響が最も大きい制約の一つです。これにより、ステートレスなインスタンスのいずれでも任意のリクエストを処理できるため、RESTがCDN、ロードバランサー、自動スケーリングと簡単に統合できる根本的な理由となっています。その代償として、認証状態は各リクエストに埋め込む必要があります(Bearerトークン)。これがJWTが普及した背景です。

メソッドセマンティクス、ステータスコードと冪等性

冪等性(繰り返し実行しても結果が変わらない性質)は、⁠クライアントが安全に再試行できるかどうかを直接決定します。ネットワークタイムアウト後に盲目的に再送信できるかどうかは、メソッドが冪等かどうかにかかっています。

メソッド   セマンティクス   安全  冪等   典型的な成功コード
GET        読み取り        是    是     200
POST       作成/アクション  否    否     201 (+ Location) / 200
PUT        全体置換        否    是     200 / 204
PATCH      部分更新        否    否*    200
DELETE     削除            否    是     204 (再度削除して404を返すのも冪等結果とみなす)
(* PATCHの冪等性はbodyのセマンティクスに依存します; JSON Merge Patchは通常冪等ですが、JSON Patchの配列操作は冪等ではありません)

ステータスコードは200/500だけでなく以下も活用してください:
  201 Created / 202 Accepted(非同期) / 204 No Content
  400 パラメータエラー · 401 認証なし · 403 認証済みだが権限なし · 404 存在しない
  409 競合 · 422 セマンティックバリデーション失敗 · 429 レート制限 (Retry-After設定推奨)
  500 サーバーエラー · 503 一時的に利用不可

並行制御⁠:ETagIf-Match を使用して楽観的ロックを実現します——クライアントは読み取ったバージョン番号を一緒に送信し、サーバー側のバージョンが変更されていた場合は 412 Precondition Failed を返します。これにより、「後書きが先書きを上書きする」問題を回避します。

リソースモデリングとURL設計

コレクション   GET    /users              リスト (フィルタ/ソート/ページネーション)
作成         POST   /users              201 + Location: /users/42
単一リソース   GET    /users/42
置換         PUT    /users/42
部分更新       PATCH  /users/42
削除         DELETE /users/42           204
サブリソース   GET    /users/42/orders
アクション     POST   /users/42/actions/reset-password   CRUD以外のアクション (明示的にラベル付け)

アンチパターン:
  GET  /getUser?id=42        URLに動詞を含める (HTTPメソッド自体が動詞である)
  POST /createUser           冗長 (POSTは作成を意味する)
  GET  /users?action=delete  危険 (GETは安全でなければなりません; 削除にはDELETEを使用すべき)

エラー処理とページネーション(過小評価されがちな設計)

エラー形式は機械可読で、サイト全体で統一されている必要があります。業界では RFC 9457 (Problem Details) に収束しています:

{ "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient funds", "status": 409,
  "detail": "Balance 10 < required 50", "instance": "/accounts/42/transfer/abc",
  "errors": [ {"field": "amount", "message": "exceeds balance"} ] }

ページネーションには主に2つの方式があり、シナリオに応じて選択します:

Offset/limit  : ?offset=40&limit=20   実装が簡単、ページジャンプ可能; 深層ページネーションで遅延 + ページ移動時にデータドリフト発生
Cursor(keyset): ?after=<opaque>&limit=20   安定しており効率的、無限スクロール/大規模データセットに適しています; ページジャンプ不可

HATEOAS

RESTの「満点」の制約:レスポンス内に利用可能なアクションへのリンクを埋め込むことで、クライアントはURLルールをハードコードするのではなく、リンクのフォローによってフローを駆動します。

{ "id": 42, "state": "pending",
  "_links": { "self":   {"href": "/orders/42"},
              "cancel": {"href": "/orders/42/actions/cancel"},
              "pay":    {"href": "/orders/42/payment"} } }

理想的には、サーバー側で自由にURLを変更でき、状態に基づいて利用可能なアクションを制御できます。現実にはほとんどのAPIで完全なHATEOASは実装されていません⁠——クライアントは通常、ドキュメントに基づいてパスをハードコードしており、その利便性が複雑さに見合っていないためです。しかし、その退化した形態は有用です:ページネーションの next リンクや、リソース内に埋め込まれたサブリソースURLは、HATEOASの考え方の実用的な残滓です。

OpenAPI / Swagger

OpenAPI(旧Swagger)は、REST APIを記述するための機械可読な仕様(YAML/JSON)であり、RESTエンジニアリングにおける事実上の標準です:

paths:
  /users/{id}:
    get:
      parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
      responses:
        '200': { content: { application/json: { schema: { $ref: '#/components/schemas/User' } } } }

その価値は、1つのspecからツールチェーン全体を派生させる点にあります:Swagger UIによるインタラクティブなドキュメント、クライアント/サーバーのコード生成、リクエスト検証とモック、契約テストなど。spec-first(契約を先に記述し、実装を後に行う)を推奨します。これにより、フロントエンドとバックエンドの並行開発が可能になり、契約がテストとなります。

REST vs GraphQL vs gRPC

互いを置き換えるものではなく、それぞれが最適な領域を持っています:

RESTGraphQLgRPC
传输/编码HTTP/1.1+ · JSONHTTP · JSONHTTP/2 · Protobuf(二进制)
取数模型每资源一端点单端点, 客户端声明字段强类型 RPC 方法
解决的痛点通用、可缓存over/under-fetching、字段多变高性能、流式、严格契约
缓存HTTP 原生(URL+ETag)难(单 POST 端点)自管
浏览器直连原生原生gRPC-web 经代理转换
典型场景公共 API、CRUD富前端/移动聚合多源内部微服务、低延迟、双向流

gRPC-web が存在する理由:ブラウザはネイティブgRPC(基盤となるHTTP/2フレーム制御を取得できないため)を直接送信できません。Envoyなどのプロキシを使用して、gRPCとブラウザで使用可能なエンコーディングの間で変換する必要があります。そのため、gRPCは主にサービス間のイントラネットで使用され、外部向けにはREST/GraphQLゲートウェイをラップすることが一般的です。

バージョン戦略

URL path     /api/v1/users          最も一般的: 直感的、キャッシュフレンドリー、ルーティングが容易   ← 推奨
Header       Accept: application/vnd.api+json; version=1   より「純粋」だが、デバッグ/キャッシュが面倒
Query        /api/users?version=1

「どこに配置するか」よりも重要なのは互換性規律です:フィールドの追加は互換性のある変更であり、バージョンを上げる必要はありません。フィールドの削除/セマンティクスの変更、必須化、型の変更は破壊的変更であり、メジャーバージョンを上げ、旧バージョンに対して明確な廃止期間(Deprecation / Sunset ヘッダー)を与える必要があります。

参考

  • REST: Roy Fielding's dissertation (2000), Chapter 5
  • OpenAPI: openapis.org · Swagger Editor · 错误格式 RFC 9457
  • GraphQL: graphql.org/learn · Apollo 文档
  • gRPC: grpc.io · grpc-web (github.com/grpc/grpc-web)

Keywords: REST, statelessness, idempotency, ETag, RFC 9457 Problem Details, cursor pagination, HATEOAS, OpenAPI, Swagger, GraphQL, over-fetching, gRPC, gRPC-web, Protobuf, API versioning