RESTful Web API の設計

ほとんどの最新の Web アプリケーションでは、クライアントがアプリケーションと対話する際に使用できる API を公開しています。 適切に設計された Web API には、次をサポートする目的があります。

  • プラットフォームの独立。 API の内部的な実装方法に関係なく、すべてのクライアントが API を呼び出すことができる必要があります。 そのためには、標準プロトコルを使用し、クライアントと Web サービスが交換するデータの形式に同意できるメカニズムを備えている必要があります。

  • サービスの進化。 Web API はクライアント アプリケーションから独立して進化し、機能を追加できる必要があります。 API の進化に伴い、既存のクライアント アプリケーションが変更なしに引き続き機能する必要があります。 クライアント アプリケーションが機能を十分に使用できるように、すべての機能が検出可能である必要があります。

このガイダンスでは、Web API の設計時に考慮すべき問題について説明します。

REST とは何か

2000 年に、ロイ・フィールディングが、Web サービスを設計するためのアーキテクチャ アプローチとして Representational State Transfer (REST) を提唱しました。 REST はハイパーメディアに基づき分散システムを構築するアーキテクチャ スタイルです。 REST は基になるプロトコルに依存せず、HTTP に必ずしも関連付けられているわけではありません。 ただし、最も一般的な REST API 実装では、アプリケーション プロトコルとして HTTP を使用します。このガイドでは、HTTP の REST API の設計に重点を置きます。

REST over HTTP の主な利点は、オープン スタンダードを使用し、API やクライアント アプリケーションの実装を特定の実装にバインドしないことです。 たとえば、REST Web サービスが ASP.NET で記述されていても、クライアント アプリケーションは任意の言語やツールセットを使用して、HTTP 要求を生成し、HTTP 応答を解析できます。

HTTP を使用した RESTful API の主な設計原則を次に示します。

  • REST API は "リソース" を中心に設計します。リソースは、クライアントがアクセスできるあらゆる種類のオブジェクト、データ、またはサービスです。

  • リソースには "識別子" があります。識別子は、そのリソースを一意に識別する URI です。 たとえば、特定の顧客注文の URI は次のようになります。

    https://adventure-works.com/orders/1
    
  • クライアントは、リソースの "表現" を交換することでサービスと対話します。 多くの Web API では、交換形式として JSON を使用します。 たとえば、上記の URI に対する GET 要求では、次の応答本文が返されます。

    {"orderId":1,"orderValue":99.90,"productId":1,"quantity":1}
    
  • REST API では、クライアントとサービスの実装の分離に役立つ統一インターフェイスを使用します。 HTTP を基盤とする REST API の場合、統一インターフェイスで標準の HTTP 動詞を使用して、リソースに対する操作を実行します。 最も一般的な操作は、GET、POST、PUT、PATCH、DELETE です。

  • REST API では、ステートレスな要求モデルを使用します。 HTTP 要求は独立しており、任意の順序で発生する可能性があるため、要求間の遷移状態の情報を保持することはできません。 情報の格納場所はリソース自体のみであり、それぞれの要求はアトミックな操作である必要があります。 この制約により、非常にスケーラブルな Web サービスが実現します。クライアントと特定のサーバー間のアフィニティを保持する必要がないためです。 どのサーバーも、任意のクライアントからの要求を処理できます。 ただし、他の要因によってスケーラビリティが制限される可能性があります。 たとえば、多くの Web サービスは、スケールアウトが難しい可能性のあるバックエンド データ ストアに書き込みを行います データ ストアのスケールアウト戦略の詳細については、データの水平的、垂直的、および機能的パーティション分割を参照してください。

  • REST API は、表現に含まれているハイパーメディア リンクによって動作します。 たとえば、次の例は注文の JSON 表現を示しています。 これには、注文に関連付けられた顧客を取得または更新するためのリンクが含まれています。

    {
        "orderID":3,
        "productID":2,
        "quantity":4,
        "orderValue":16.60,
        "links": [
            {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"GET" },
            {"rel":"product","href":"https://adventure-works.com/customers/3", "action":"PUT" }
        ]
    }
    

2008 年に、レオナルド・リチャードソンが Web API の次の成熟度モデルを提唱しました。

  • Level 0:1 つの URI を定義し、すべての操作がこの URI に対する POST 要求です。
  • Level 1: リソースごとに個別の URI を作成します。
  • Level 2: HTTP メソッドを使用して、リソースに対する操作を定義します。
  • Level 3: ハイパーメディアを使用します (後述の HATEOAS)。

フィールディングの定義に従うと、レベル 3 が真の RESTful API になります。 実際には、公開されている多くの Web API がほぼレベル 2 に位置します。

リソースを中心とした API 設計の整理

Web API が公開するビジネス エンティティに注目して説明します。 たとえば、電子商取引システムでは、主エンティティは顧客と注文です。 注文の作成は、注文情報が含まれた HTTP POST 要求を送信することで実現できます。 HTTP 応答では、注文が正常に行われたかどうかを示します。 可能であれば、リソース URI は動詞 (リソースに対する操作) ではなく、名詞 (リソース) に基づくようにします。

https://adventure-works.com/orders // Good

https://adventure-works.com/create-order // Avoid

リソースは、単一の物理データ項目に基づく必要はありません。 たとえば、注文リソースをリレーショナル データベースの複数のテーブルとして内部的に実装しながら、クライアントには単一のエンティティとして表示することができます。 データベースの内部構造を単にミラー化する API は作成しないようにします。 REST の目的は、エンティティと、アプリケーションがそれらのエンティティに対して実行できる操作をモデル化することです。 クライアントを内部実装に公開しないでください。

多くの場合、エンティティはコレクション (orders、customers) にグループ化されます。 コレクションはコレクションの項目とは別のリソースであり、独自の URI が必要です。 たとえば、次の URI は注文のコレクションを表しています。

https://adventure-works.com/orders

コレクション URI に HTTP GET 要求を送信することで、コレクションの項目の一覧を取得します。 コレクションの各項目にも、それぞれ一意の URI があります。 項目の URI に対する HTTP GET 要求では、その項目の詳細が返されます。

URI には一貫性のある名前付け規則を使用します。 一般に、コレクションを参照する URI には複数形の名詞を使用すると便利です。 コレクションと項目の URI は、階層構造に整理することをお勧めします。 たとえば、/customers は customers コレクションのパスであり、/customers/5 は ID が 5 の顧客のパスです。 この方法は、Web API を直感的に保つのに役立ちます。 また、多くの Web API フレームワークでは、パラメーター化された URI パスに基づいて要求をルーティングできるので、パス /customers/{id} のルートを定義できます。

さまざまな種類のリソース間の関係と、これらの関連付けを公開する方法も検討します。 たとえば、/customers/5/orders は顧客 5 のすべての注文を表します。 逆方向にし、/orders/99/customer などの URI を使用して、注文から顧客への関連付けを表すこともできます。 ただし、このモデルを拡張しすぎると、実装が煩雑になる可能性があります。 より優れたソリューションは、HTTP 応答メッセージの本文に、関連するリソースに誘導できるリンクを含めることです。 このメカニズムについては、「HATEOAS を使用した関連リソースへのナビゲーションの実現」のセクションで詳しく説明します。

複雑なシステムでは、クライアント が複数のレベルの関係をナビゲートできるようにする URI (例: /customers/1/orders/99/products) を提供したくなる可能性があります。 ただし、将来的にリソース間のリレーションシップが変わる場合、このレベルの複雑さを維持するのは難しく、柔軟性がありません。 代わりに、URI を比較的単純に保つように努めます。 アプリケーションがリソースへの参照を取得したら、この参照を使用してそのリソースに関連する項目を検索できるようにします。 前述のクエリを URI /customers/1/orders に置き換えると、顧客 1 のすべての注文を検索でき、次に /orders/99/products に置き換えると、この注文の製品を検索できます。

ヒント

"コレクション/項目/コレクション" よりも複雑なリソース URI を要求しないでください。

Web 要求はいずれも Web サーバーに負荷をかけるという点も考慮します。 要求が増えるほど、負荷も大きくなります。 そのため、多数の小さなリソースを公開する "おしゃべりな" Web API にならないようにしてください。 このような API では、クライアント アプリケーションが必要なすべてのデータを検索するために、複数の要求を送信することが必要になる場合があります。 代わりに、データを非正規化し、1 つの要求で取得できるより大きなリソースに関連情報をまとめることができます。 ただし、このアプローチでは、クライアントが必要としないデータをフェッチするオーバーヘッドとバランスを取る必要があります。 ラージ オブジェクトを取得すると、要求の待機時間が増加し、追加の帯域幅コストが発生する可能性があります。 これらのパフォーマンスのアンチパターンの詳細については、頻度の高い I/O に関する記事、および余分なフェッチに関する記事をご覧ください。

Web API と基になるデータ ソース間に依存関係を導入しないようにします。 たとえば、データがリレーショナル データベースに格納されている場合、Web API は各テーブルをリソースのコレクションとして公開する必要はありません。 実際には、これは不適切な設計と考えられます。 代わりに、Web API をデータベースの抽象化と捉えます。 必要に応じて、データベースと Web API 間のマッピング レイヤーを導入します。 これにより、クライアント アプリケーションは基になるデータベース スキームの変更から分離されます。

最後に、Web API により実装されるすべての操作を特定のリソースにマッピングできない場合があります。 機能を呼び出し、HTTP 応答メッセージとして結果を返す HTTP 要求によって、そのような "非リソース" シナリオに対応できます。 たとえば、加算や減算などの単純な電卓操作を実装する Web API は、これらの操作を疑似リソースとして公開し、クエリ文字列を使用して必要なパラメーターを指定する URI を提供できます。 たとえば、URI /add?operand1=99&operand2=1 に対する GET 要求では、本文に値 100 が含まれた応答メッセージが返されます。 ただし、これらの形式の URI は常に慎重に使用してください。

HTTP メソッドに関する API 操作の定義

HTTP プロトコルにより、要求にセマンティックな意味を割り当てる多くのメソッドが定義されます。 ほとんどの RESTful Web API により使用される一般的な HTTP メソッドは次のとおりです。

  • GET: 指定した URI のリソースの表現を取得します。 応答メッセージの本文には、要求されたリソースの詳細が含まれています。
  • POST: 指定した URI の新しいリソースを作成します。 応答メッセージの本文には、新しいリソースの詳細が記載されています。 POST は実際にはリソースを作成しない操作をトリガーするのにも使用できます。
  • PUT: 指定した URI のリソースを作成または置換します。 要求メッセージの本文で、作成または更新するリソースを指定します。
  • PATCH: リソースの部分的な更新を実行します。 要求本文でリソースに適用する一連の変更を指定します。
  • DELETE: 指定した URI のリソースを削除します。

特定の要求の影響は、リソースがコレクションと個々の項目のどちらであるかによって異なります。 以下の表では、電子商取引の例を使用しながら、ほとんどの RESTful の実装で使用される一般的な規則をまとめています。 これらの要求がすべて実装されない場合があります—具体的なシナリオにより異なります。

リソース POST GET PUT DELETE
/customers 新しい顧客を作成 すべての顧客を取得 顧客を一括更新 すべての顧客を削除
/customers/1 エラー 顧客 1 の詳細を取得 顧客 1 の詳細を更新 (顧客 1 が存在する場合) 顧客 1 を削除
/customers/1/orders 顧客 1 の新しい注文を作成 顧客 1 のすべての注文を取得 顧客 1 の注文を一括更新 顧客 1 のすべての注文を削除

POST、PUT、PATCH の違いがわかりにくい可能性があります。

  • POST 要求ではリソースを作成します。 サーバーは新しいリソースの URI を割り当て、その URI をクライアントに返します。 REST モデルでは、POST 要求をコレクションに適用することがよくあります。 新しいリソースがコレクションに追加されます。 POST 要求を使用して、新しいリソースを作成せずに、処理するデータを既存のリソースに送信することもできます。

  • PUT 要求では、リソースの作成 または 既存のリソースの更新を行います。 クライアントがリソースの URI を指定します。 要求本文には、リソースの完全な表現が含まれます。 この URI を使用するリソースが既に存在する場合は、そのリソースが置き換えられます。 それ以外の場合は、新しいリソースが作成されます (サーバーが作成をサポートしている場合)。 PUT 要求は、コレクションではなく、個々の項目 (特定の顧客など) であるリソースに最も頻繁に適用されます。 サーバーが PUT を介した更新をサポートしていても、作成はサポートしていないことがあります。 PUT を介した作成をサポートするかどうかは、リソースが存在する前に、クライアントが意味のある方法でリソースに URI を割り当てることができるかどうかに左右されます。 できない場合は、POST を使用してリソースを作成し、PUT または PATCH を使用して更新します。

  • PATCH 要求では、既存のリソースに対して "部分的な更新" を実行します。 クライアントがリソースの URI を指定します。 要求本文で、リソースに適用する一連の "変更" を指定します。 クライアントは、リソースの表現全体ではなく、変更のみを送信するため、PUT を使用するよりも効率的です。 技術的には、PATCH で ("null" リソースに対する一連の更新を指定することによって) 新しいリソースを作成することもできます (サーバーがこれをサポートしている場合)。

PUT 要求はべき等である必要があります。 クライアントが同じ PUT 要求を複数回送信した場合、結果は常に同じになります (同じリソースが同じ値で変更されます)。 POST 要求と PATCH 要求はべき等である保証はありません。

HTTP セマンティクスへの準拠

このセクションでは、HTTP 仕様に準拠した API の設計に関する一般的な考慮事項について説明します。 ただし、考えられるすべての詳細やシナリオを取り上げているわけではありません。 不確かな場合は、HTTP 仕様を参照してください。

メディアの種類

前述のように、クライアントとサーバーはリソースの表現を交換します。 たとえば、POST 要求では、要求本文に作成するリソースの表現が含まれます。 GET 要求では、応答本文にフェッチされたリソースの表現が含まれます。

HTTP プロトコルでは、"メディアの種類" (MIME の種類とも呼ばれます) を使用して形式が指定されます。 非バイナリ データの場合、ほとんどの Web API が JSON (メディアの種類 = application/json) をサポートしており、場合によっては XML (メディアの種類 = application/xml) もサポートしています。

要求または応答の Content-Type ヘッダーでは、表現の形式を指定します。 JSON データを含む POST 要求の例を次に示します。

POST https://adventure-works.com/orders HTTP/1.1
Content-Type: application/json; charset=utf-8
Content-Length: 57

{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}

サーバーがこのメディアの種類をサポートしていない場合、HTTP 状態コード 415 (Unsupported Media Type) が返されます。

クライアント要求には、クライアントが応答メッセージでサーバーから受け入れるメディアの種類の一覧を含む Accept ヘッダーを含めることができます。 次に例を示します。

GET https://adventure-works.com/orders/2 HTTP/1.1
Accept: application/json

サーバーが記載されているメディアの種類のいずれかに対応できない場合、HTTP 状態コード406 (Not Acceptable) が返されます。

GET メソッド

GET メソッドが成功すると、通常は HTTP 状態コード200 (OK) が返されます。 リソースが見つからない場合、メソッドは 404 (Not Found) を返します。

POST メソッド

POST メソッドは、新しいリソースを作成すると、HTTP 状態コード 201 (Created) を返します。 新しいリソースの URI は、応答の Location ヘッダーに含まれています。 応答本文には、リソースの表現が含まれています。

メソッドが新しいリソースの作成以外の処理を実行した場合は、HTTP 状態コード 200 を返し、応答本文に操作の結果を含めることができます。 また、返す結果がない場合、メソッドは応答本文なしで HTTP 状態コード 204 (No Content) を返すことができます。

クライアントが要求に無効なデータを挿入した場合、サーバーは HTTP 状態コード 400 (Bad Request) を返します。 応答本文には、エラーに関する追加情報または詳細を提供する URI へのリンクを含めることができます。

PUT メソッド

POST メソッドと同様に、PUT メソッドは新しいリソースを作成すると、HTTP 状態コード 201 (Created) を返します。 既存のリソースを更新した場合は、200 (OK) または 204 (No Content) を返します。 既存のリソースを更新できない場合もあります。 その場合は、HTTP 状態コード 409 (Conflict) を返すことを検討してください。

コレクションの複数のリソースの更新をバッチ処理できる一括 HTTP PUT 操作の実装を検討してください。 PUT 要求はコレクションの URI を指定し、要求本文は変更するリソースの詳細を指定する必要があります。 この方法によりおしゃべりを減らし、パフォーマンスを向上させることができます。

PATCH メソッド

PATCH 要求では、クライアントが パッチ ドキュメント の形で既存のリソースに対する一連の更新を送信します。 サーバーはパッチ ドキュメントを処理して更新を実行します。 パッチ ドキュメントは、リソース全体を記述するのではなく、適用する一連の変更のみを記述します。 PATCH メソッドの仕様 (RFC 5789) では、パッチ ドキュメントの特定の形式は定義されていません。 形式は、要求内のメディアの種類から推論する必要があります。

JSON が Web API の最も一般的なデータ形式と考えられます。 主な JSON ベースのパッチ形式として、JSON パッチJSON マージ パッチ の 2 つがあります。

JSON マージ パッチの方が若干シンプルです。 パッチ ドキュメントは元の JSON リソースと同じ構造ですが、変更または追加する必要のあるフィールドのサブセットだけが含まれています。 また、パッチ ドキュメントのフィールド値に null を指定することによって、そのフィールドを削除できます (つまり、元のリソースに明示的な null 値を含めることができる場合、マージ パッチは適していません)。

たとえば、元のリソースが次の JSON 表現を持つとします。

{
    "name":"gizmo",
    "category":"widgets",
    "color":"blue",
    "price":10
}

このリソースの使用可能な JSON マージ パッチを次に示します。

{
    "price":12,
    "color":null,
    "size":"small"
}

これは、price を更新し、color を削除し、size を追加するようサーバーに指示する一方、namecategory は変更されません。 JSON マージ パッチの詳細については、RFC 7396 を参照してください。 JSON マージ パッチのメディアの種類は application/merge-patch+json です。

マージ パッチでは、パッチ ドキュメントの null に特別な意味があるため、元のリソースに明示的な null 値を含めることができる場合、マージ パッチは適していません。 また、パッチ ドキュメントでは、サーバーが更新を適用する順序は指定されていません。 データとドメインによっては、これが重要な場合とそうでない場合があります。 RFC 6902 で定義されている JSON パッチの方が柔軟性に優れています。 適用する一連の操作として変更を指定します。 操作には、追加、削除、置換、コピー、テスト (値の検証) があります。 JSON パッチのメディアの種類は application/json-patch+json です。

PATCH 要求を処理するときに発生する可能性のある一般的なエラー状態と、該当する HTTP 状態コードを次に示します。

エラー状態 HTTP 状態コード
パッチ ドキュメントの形式がサポートされていません。 415 (Unsupported Media Type)
無効な形式のパッチ ドキュメントです。 400 (Bad Request)
パッチ ドキュメントは有効ですが、現在の状態のリソースに変更を適用することはできません。 409 (Conflict)

DELETE メソッド

削除操作が成功すると、Web サーバーは HTTP 状態コード 204 (No Content) で応答します。これは、プロセスは正常に処理されたが、応答本文にそれ以上の情報は含まれていないことを示します。 リソースが存在しない場合、Web サーバーは HTTP 404 (Not Found) を返すことができます。

非同期操作

POST、PUT、PATCH、または DELETE 操作では、完了までに時間のかかる処理が必要な場合があります。 完了を待ってからクライアントに応答を送信すると、許容できない待機時間が発生する可能性があります。 その場合は、操作を非同期にすることを検討してください。 HTTP 状態コード 202 (Accepted) を返して、要求が処理のために受理されたが、まだ完了していないことを示します。

クライアントが状態エンドポイントをポーリングして状態を監視できるように、非同期要求の状態を返すエンドポイントを公開する必要があります。 202 応答の Location ヘッダーに、状態エンドポイントの URI を含めます。 次に例を示します。

HTTP/1.1 202 Accepted
Location: /api/status/12345

クライアントがこのエンドポイントに GET 要求を送信した場合、応答に要求の現在の状態を含める必要があります。 必要に応じて、完了までの推定時間や操作を取り消すためのリンクを含めることもできます。

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"In progress",
    "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

非同期操作で新しいリソースを作成する場合、状態エンドポイントは、操作の完了後に状態コード 303 (See Other) を返す必要があります。 303 応答には、新しいリソースの URI を示す Location ヘッダーを含めます。

HTTP/1.1 303 See Other
Location: /api/orders/12345

詳細については、「Asynchronous Request-Reply pattern」(非同期要求-応答パターン) を参照してください。

データのフィルター処理とページング処理

単一の URI を使用してリソースのコレクションを公開すると、情報のサブセットだけが必要な場合に、アプリケーションが大量のデータをフェッチする可能性があります。 たとえば、クライアント アプリケーションが、コストが特定の値を超えるすべての注文を検索する必要があるとします。 /orders URI からすべての注文を取得し、クライアント側でこれらの注文をフィルター処理します。 このプロセスが非常に非効率的であることは明らかです。 Web API をホストするサーバーのネットワーク帯域幅と処理能力が無駄になります。

代わりに、API は URI のクエリ文字列でフィルター (例: /orders?minCost=n) を渡すことを許可することができます。 Web API は、クエリ文字列の minCost パラメーターを解析して処理し、サーバー側でフィルター処理された結果を返す役割を担います。

コレクション リソースに対する GET 要求では、多数の項目が返される可能性があります。 1 つの要求で返されるデータの量を制限するように Web API を設計する必要があります。 取得する項目の最大数とコレクションの開始オフセットを指定するクエリ文字列のサポートを検討します。 次に例を示します。

/orders?limit=25&offset=50

サービス拒否攻撃を防ぐために、返される項目の数に上限を課すことも検討してください。 クライアント アプリケーションを支援するには、改ページ調整されたデータを返す GET 要求に、コレクションで利用できるリソースの合計を示す何らかの形式のメタデータも含まれるようにします。

同様の方法を使用して、フェッチされたデータを並べ替えることができます。/orders?sort=ProductID のように、フィールド名を値として取得する並べ替えパラメーターを指定します。 ただし、クエリ文字列パラメーターは、キャッシュ データのキーとして多くのキャッシュ実装で使用されるリソース識別子の一部となるため、この方法はキャッシュに悪影響を与える可能性があります。

各項目に大量のデータが含まれている場合、返されるフィールドを項目ごとに制限するように、この方法を拡張できます。 たとえば、 /orders?fields=ProductID,Quantity など、コンマ区切りのフィールド一覧を受け取るクエリ文字列パラメーターを使用することができます。

クエリ文字列内の省略可能なすべてのパラメーターに、意味のある既定値を設定します。 たとえば、改ページ調整を実装する場合は limit パラメーターを 10、offset パラメーターを 0 に設定し、順序付けを実装する場合はリソースのキーに並べ替えパラメーターを設定し、プロジェクションをサポートする場合はfields パラメーターをリソースのすべてのフィールドに設定します。

大きなバイナリ リソースの部分的な応答のサポート

リソースに大きなバイナリ フィールド (ファイルや画像など) が含まれている場合があります。 信頼性の低い断続的な接続に起因する問題を克服し、応答時間を向上させるには、このようなリソースをチャンクで取得できるようにすることを検討します。 そのためには、Web API が大きなリソースの GET 要求で Accept-Ranges ヘッダーをサポートする必要があります。 このヘッダーは、GET 操作が部分的な要求をサポートすることを示します。 クライアント アプリケーションは、バイト範囲として指定されたリソースのサブセットを返す GET 要求を送信できます。

これらのリソースの HTTP HEAD 要求を実装することも検討してください。 HEAD 要求は GET 要求に似ていますが、空のメッセージ本文と共に、リソースを示す HTTP ヘッダーだけを返す点が異なります。 クライアント アプリケーションは HEAD 要求を発行し、部分的 GET 要求を使用して、リソースを取得するかどうかを判断します。 次に例を示します。

HEAD https://adventure-works.com/products/10?fields=productImage HTTP/1.1

応答メッセージの例を次に示します。

HTTP/1.1 200 OK

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 4580

Content-Length ヘッダーはリソースの合計サイズを示し、Accept-Ranges ヘッダーは対応する GET 操作が部分的な結果をサポートすることを示します。 クライアント アプリケーションは、この情報を使用して画像を小さなチャンクで取得できます。 最初の要求では、Range ヘッダーを使用して最初の 2,500 バイトを取得します。

GET https://adventure-works.com/products/10?fields=productImage HTTP/1.1
Range: bytes=0-2499

応答メッセージは、HTTP 状態コード 206 を返すことで、これが部分的な応答であることを示します。 Content-Length ヘッダーはメッセージ本文で返される実際のバイト数を指定し (リソースのサイズではない)、Content-Range ヘッダーはこれがどの部分のリソースであるかを示します (4,580 のうち 0 ~ 2499 バイト):

HTTP/1.1 206 Partial Content

Accept-Ranges: bytes
Content-Type: image/jpeg
Content-Length: 2500
Content-Range: bytes 0-2499/4580

[...]

クライアント アプリケーションのその後の要求で、リソースの残りの部分を取得できます。

REST の背後にある主な動機の 1 つは、URI スキームの事前知識を必要とせずに、リソースのセット全体を移動できることです。 各 HTTP GET 要求は応答に含まれるハイパーリンクより、要求したオブジェクトに直接関連するリソースを検索するのに必要な情報を返し、これらの各リソースで使用可能な操作を記述する情報も提供されます。 この原則は HATEOAS、つまり アプリケーション状態のエンジンとしてのハイパーテキストとして知られます。 システムは効率的に Finite State Machine であり、各要求への応答には、ある状態を別の状態に移すのに必要な情報が含まれています。他の情報は必要ありません。

注意

現在、HATEOAS の原則をモデル化する方法を定義する汎用の標準はありません。 このセクションで示す例は、考えられる 1 つの専用ソリューションを示しています。

たとえば、注文と顧客の関係を処理するために、注文の表現にその注文の顧客に対して使用できる操作を示すリンクを含めることができます。 使用可能な表現を次に示します。

{
  "orderID":3,
  "productID":2,
  "quantity":4,
  "orderValue":16.60,
  "links":[
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"customer",
      "href":"https://adventure-works.com/customers/3",
      "action":"DELETE",
      "types":[]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"GET",
      "types":["text/xml","application/json"]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"PUT",
      "types":["application/x-www-form-urlencoded"]
    },
    {
      "rel":"self",
      "href":"https://adventure-works.com/orders/3",
      "action":"DELETE",
      "types":[]
    }]
}

この例では、links 配列に一連のリンクが含まれています。 各リンクは、関連エンティティに対する操作を表しています。 各リンクのデータには、関係 ("customer")、URI (https://adventure-works.com/customers/3)、HTTP メソッド、サポートされる MIME の種類が含まれています。 クライアント アプリケーションが操作を呼び出すことができるようにするために必要な情報はこれですべてです。

links 配列には、取得したリソース自体に関する自己参照型の情報も含まれています。 これらの情報の関係は self です。

返されるリンクのセットは、リソースの状態によって変わる可能性があります。 "アプリケーション状態のエンジン" であるハイパーリンクとはこのことです。

RESTful Web API のバージョン管理

Web API が更新されないことはありえません。 ビジネスの要求は変化するため、新しいリソースのコレクションが加わり、リソース間のリレーションシップが変化し、リソース内のデータ構造が修正される可能性があります。 新しいまたは異なる要件を処理するために Web API を更新することは比較的簡単なプロセスですが、そのような変化が Web API を消費するクライアント アプリケーションに対して与える影響を考慮する必要があります。 問題なのは、Web API を設計および実装している開発者はその API を完全に制御できるものの、リモートで操作しているサード パーティの組織により構築されたクライアント アプリケーションを、その開発者が同程度には制御できないことです。 まず必要なのは、新しいクライアント アプリケーションが新しい機能やリソースを活用できるようにしながら、既存のアプリケーションに変更なく機能を続行させることです。

バージョン管理により Web API は公開する機能およびリソースを示すことができ、クライアント アプリケーションは特定のバージョンの機能またはリソースへの要求を送信できます。 次のセクションではいくつかの方法について説明しますが、それぞれに独自の利点とトレードオフがあります。

バージョン管理なし

これは最も単純な方法で、一部の内部 API で許容されます。 大きな変更は新しいリソースまたは新しいリンクとして示されます。 既存のリソースにコンテンツを追加しても、このコンテンツの表示を想定していないクライアント アプリケーションはそれを無視するため、重大な変更はありません。

たとえば、URI https://adventure-works.com/customers/3 への要求により、次のクライアント アプリケーションにより期待される idname、および address フィールドを含む単一の顧客に関する詳細が返されます。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

注意

わかりやすくするために、このセクションで示す応答例には HATEOAS リンクは含まれていません。

DateCreated フィールドが顧客リソースのスキーマに追加される場合、応答は次のようになります。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":"1 Microsoft Way Redmond WA 98053"}

既存のクライアント アプリケーションが認識されていないフィールドを無視できる場合は、正常に機能を続行する場合がありますが、新しいクライアント アプリケーションではこの新しいフィールドを処理するように設計できます。 とはいえ、リソースのスキーマにさらに重大な変更があるか (フィールドの削除や名前の変更など)、リソース間のリレーションシップが変更される場合、これらは既存のクライアント アプリケーションが正常に機能できなくなる重大な変更となる可能性があります。 このような状況では、次の方法のいずれかを検討してください。

URI のバージョン管理

Web API を変更するか、リソースのスキーマを変更するたびに、バージョン番号を各リソースの URI に追加します。 既存の URI はこれまでと同様に動作を続け、元のスキーマに準拠するリソースを返します。

前の例を拡張し、address フィールドをアドレスの各構成部分 (streetAddresscitystatezipCode など) を含むサブフィールドに再構築する場合、このバージョンのリソースはバージョン番号を含む https://adventure-works.com/v2/customers/3 などの URI より公開できます。

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

このバージョン管理メカニズムは非常に単純ですが、適切なエンドポイントに要求をルーティングするサーバーにより変わります。 とはいえ、Web API は何度も繰り返すことで成熟し、サーバーは多くの異なるバージョンをサポートする必要があるため、扱いにくくなる可能性があります。 また、純正主義者の観点からすると、すべての場合でクライアント アプリケーションは同じデータを取得しているため (顧客 3)、URI はバージョンによってそれほど異なるべきではありません。 すべてのリンクで URI にバージョン番号が含まれる必要があるため、このスキームによっても HATEOAS の実装が複雑になります。

クエリ文字列のバージョン管理

複数の URI を提供するのではなく、https://adventure-works.com/customers/3?version=2 など、HTTP 要求に追加されたクエリ文字列内のパラメーターを使用してリソースのバージョンを指定できます。 バージョン パラメーターがより古いクライアント アプリケーションで省略される場合、1 などの有効な値を既定にします。

この方法には同じリソースからは常に同じ URI が取得されるというセマンティックな利点がありますが、クエリ文字列を解析し、適切な HTTP 応答を返送する要求を処理するコードにより異なります。 この方法は、URI のバージョン管理メカニズムとして HATEOAS を実装する同様の複雑さによっても影響されます。

注意

一部の古い Web ブラウザーや Web プロキシは、URI にクエリ文字列を含む要求の応答をキャッシュしません。 これは、Web API を使用し、そのような Web ブラウザー内から実行する Web アプリケーションのパフォーマンスを低下させる可能性があります。

ヘッダーのバージョン管理

バージョン番号をクエリ文字列パラメーターとして追加するのではなく、リソースのバージョンを示すカスタム ヘッダーを実装できます。 この方法ではクライアント アプリケーションにより任意の要求に適切なヘッダーを追加する必要がありますが、バージョン ヘッダーが省略されている場合、クライアントの要求を処理するコードは既定値 (バージョン 1) を使用できます。 次の例では、Custom-Header という名前のカスタム ヘッダーを使用します。 このヘッダーの値は、Web API のバージョンを示します。

バージョン 1:

GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

バージョン 2:

GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=2
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":3,"name":"Contoso LLC","dateCreated":"2014-09-04T12:11:38.0376089Z","address":{"streetAddress":"1 Microsoft Way","city":"Redmond","state":"WA","zipCode":98053}}

前の 2 つの方法と同様、HATEOAS の実装では任意のリンクに適切なカスタム ヘッダーを追加することが必要です。

メディアの種類のバージョン管理

クライアント アプリケーションが Web サーバーに HTTP GET 要求を送信する場合、このガイダンスで既に説明したように、Accept ヘッダーを使用して処理できるコンテンツの書式を指定する必要があります。 多くの場合、Accept ヘッダーの目的は、応答の本文が XML、JSON、またはクライアントが解析可能な他の一般的な形式であるかどうかをクライアント アプリケーションが指定できるようにすることです。 とはいえ、想定しているリソースのバージョンをクライアント アプリケーションが示すことができるようにする情報を含むカスタム メディアの種類を定義できます。 次の例は、application/vnd.adventure-works.v1+json の値を含む Accept ヘッダーを指定する要求を示します。 vnd.adventure-works.v1 要素は Web サーバーに対し、バージョン 1 のリソースを返すように指示しますが、json 要素は応答本文の形式が JSON であるように指定します。

GET https://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json

要求を処理しているコードは、Accept ヘッダーを処理し、可能な限りそれを使用します (クライアント アプリケーションは複数の形式を Accept ヘッダーで指定する場合があり、その場合 Web サーバーは最も適切な形式を応答本文に選択できます)。 Web サーバーは次のように Content-Type ヘッダーを使用することで、応答本文のデータの形式を確認します。

HTTP/1.1 200 OK
Content-Type: application/vnd.adventure-works.v1+json; charset=utf-8

{"id":3,"name":"Contoso LLC","address":"1 Microsoft Way Redmond WA 98053"}

Accept ヘッダーにより既知のメディアの種類が指定されない場合、Web サーバーは HTTP 406 (Not Acceptable) 応答メッセージを生成するか、既定のメディアの種類でメッセージを返すことができます。

この方法が最も純粋と思われるバージョン管理メカニズムで、当然ながら HATEOAS で役立ち、リソース リンクの関連データの MIME の種類を含めることができます。

注意

バージョン管理の方法を選択すると、パフォーマンスに対する影響、特に Web サーバーでのキャッシュについて検討する必要もあります。 同じ URI/クエリ文字列の組み合わせは同じデータを毎回参照するため、URI バージョン管理および Query String バージョン管理スキームはキャッシュに適しています。

通常、Header バージョン管理および Media Type バージョン管理メカニズムでは、カスタム ヘッダーまたは Accept ヘッダーの値を確認するのに追加のロジックが必要です。 大規模な環境では、多くのクライアントで異なるバージョンの Web API が使用されているため、サーバー側のキャッシュの重複データ量が著しく増加します。 この問題が深刻になるのは、クライアント アプリケーションがキャッシュを実装するプロキシを経由して Web サーバーと通信する場合です。また、キャッシュに要求されたデータのコピーが現在保持されていない場合は Web サーバーに要求の転送のみ行います。

Open API イニシアチブ

Open API イニシアチブは、ベンダー間の REST API の記述を標準化するために業界コンソーシアムによって作成されました。 このイニシアチブの一環として、Swagger 2.0 仕様という名前が OpenAPI 仕様 (OAS) に変更され、Open API イニシアチブの下に配置されました。

Web API に OpenAPI を採用することもできます。 考慮すべき点:

  • OpenAPI 仕様には、REST API の設計方法に関する一連の厳密なガイドラインが付属しています。 相互運用性の利点はありますが、API を設計するときは、仕様に準拠するようにさらに注意が必要です。

  • OpenAPI では、実装優先のアプローチではなく、コントラクト優先のアプローチが推進されます。 コントラクト優先とは、API コントラクト (インターフェイス) を最初に設計してから、コントラクトを実装するコードを記述することを意味します。

  • Swagger などのツールにより、API コントラクトからクライアント ライブラリまたはドキュメントを生成できます。 例については、Swagger を使用する ASP.NET Web API のヘルプ ページに関する記事をご覧ください。

詳細情報