RESTful API データモデリング

RESTful APIのデータモデリング:複合キーを持つリソースの識別と操作

Tags: データモデリング, RESTful API, API設計, 複合キー, リソース識別

はじめに

多くの業務システムでは、データベースにおいてテーブルの主キーが複数の列で構成される「複合キー」が使用されています。例えば、注文明細のように「注文ID」と「明細番号」の組み合わせで一意になるデータや、中間テーブルのように「ユーザーID」と「グループID」の組み合わせで関係性を表すデータなどがこれにあたります。

このような複合キーを持つデータをRESTful APIで扱う際、どのようにリソースとして定義し、APIエンドポイントのURL設計やリクエスト・レスポンスのデータ構造を考慮すべきか、判断に迷うことがあるかもしれません。RESTfulな設計原則では、リソースは一意な識別子(ID)によって特定されることが基本とされます。この一意なIDという考え方と、複数の要素で構成される複合キーをどのように整合させるかが課題となります。

本記事では、RESTful APIにおいて複合キーを持つリソースをどのようにモデリングし、識別および操作するための具体的なアプローチについて解説します。

RESTful APIにおけるリソースの識別

RESTful APIの重要な原則の一つは、URI (Uniform Resource Identifier) を用いてリソースを一意に識別することです。通常、これは /resources/{id} のような形式で表現され、{id} にはリソースの単一のユニークな識別子が入ります。

複合キーを持つリソースの場合、この単一の識別子として表現することが難しい場合があります。例えば、OrderLine というリソースが orderIdlineNumber の複合キーを持つとします。これをどのようにURLで表現すれば、RESTfulな原則に沿いつつ、リソースを一意に特定できるでしょうか。

複合キーを持つリソースのURL設計パターン

複合キーを持つリソースを識別するためのURL設計には、いくつかのパターンが考えられます。それぞれのパターンにはメリットとデメリットがあります。

パターン1: 複合キーの各要素をパスパラメータとして使用する

最もRESTfulなアプローチに近いとされるのが、複合キーを構成する要素をパスパラメータとしてURLに含める方法です。

例:/orders/{orderId}/lines/{lineNumber}

この設計は、親子関係や階層構造を持つリソース(この例では「注文」に対する「明細」)を表現するのに適しています。orderId によって特定の注文が識別され、その子リソースである明細が lineNumber によって識別されるという構造がURLから直感的に理解できます。

パターン2: 複合キーの各要素をクエリパラメータとして使用する

複合キーの要素をクエリパラメータとして渡す方法です。

例:/orderLines?orderId={orderId}&lineNumber={lineNumber}

このパターンは、リソースの「検索」や「フィルタリング」にクエリパラメータを使用するというRESTfulな原則からは外れます。クエリパラメータは、リソースの識別ではなく、既に識別されたリソースのコレクションに対する操作(絞り込み、ソートなど)に使用されるのが一般的です。

パターン3: 複合キーの要素を組み合わせて単一のIDとする

複合キーの要素を何らかのルールで結合し、単一の文字列としてパスパラメータに含める方法です。

例:/orderLines/{combinedId} (例: {orderId}-{lineNumber} のように結合)

この方法では、APIの内部で {combinedId} から元の orderIdlineNumber を復元する必要があります。結合ルールが明確で、かつ結合後の文字列が一意になることが前提となります。

推奨パターン

複合キーを持つリソースを一意に識別する場合、パターン1 (/{key1}/{key2}/...) が最もRESTfulな原則に沿っており、推奨されるアプローチです。特に、キー要素が少なく、リソース間に階層関係がある場合に有効です。

ただし、複合キーの要素が多数にわたる場合や、階層関係が複雑でない場合は、URLが不必要に長くなる、あるいは複雑になる可能性があります。その場合は、リソースの識別方法としてUUIDなどの単一の代理キーを導入し、複合キーはリソースのプロパティとして扱うという代替案も検討できます。しかし、本記事は「複合キーを持つリソースを複合キーで識別・操作する」というテーマに絞って解説を進めます。

CRUD操作とデータモデリング

URLでのリソース識別方法が決まったら、次にCRUD操作(作成、取得、更新、削除)におけるリクエスト・レスポンスのデータ構造をモデリングします。

ここでは、パターン1 (/orders/{orderId}/lines/{lineNumber}) を採用し、OrderLine リソースが orderId (string), lineNumber (integer), item (string), quantity (integer) というプロパティを持つと仮定します。

データの取得 (GET)

特定のリソースを取得するAPIエンドポイントは次のようになります。

GET /orders/{orderId}/lines/{lineNumber}

レスポンスボディには、取得した OrderLine リソースの表現を含めます。複合キーである orderIdlineNumber も、リソースのプロパティとしてレスポンスに含めるのが一般的です。これにより、クライアントは取得したデータがどのリソースであるかを明確に認識できます。

レスポンスボディ (JSON例):

{
  "orderId": "ORD12345",
  "lineNumber": 1,
  "item": "Laptop",
  "quantity": 1,
  "links": [
    {
      "rel": "self",
      "href": "/orders/ORD12345/lines/1"
    },
    {
      "rel": "order",
      "href": "/orders/ORD12345"
    }
  ]
}

links プロパティはHATEOASの考え方を取り入れたもので、関連リソースへのナビゲーション情報を提供します。ここでは、自身のURI (self) と親リソースである注文 (order) へのリンクを含めています。

データの作成 (POST)

新しいリソースを作成する場合、通常は親リソースのコレクションエンドポイントに対してPOSTリクエストを行います。複合キーの一部(この例では orderId)はURLで特定され、残りの複合キー要素 (lineNumber) とリソースの他のプロパティはリクエストボディに含めます。

POST /orders/{orderId}/lines

リクエストボディには、作成したい OrderLine の情報を指定します。orderId はURLで指定されているためボディに含める必要はありませんが、混乱を避けるために含めることもあります(ただし、URLの値とボディの値が異なる場合はエラーとすべきです)。lineNumber は通常、このリクエストで新規採番される場合もありますが、クライアントが指定する場合もあります。ここではクライアントが指定するケースを想定します。

リクエストボディ (JSON例):

{
  "lineNumber": 2,
  "item": "Mouse",
  "quantity": 2
}

レスポンス:

成功時には 201 Created ステータスコードを返し、Location ヘッダーに作成されたリソースのURIを含めるのがRESTfulなプラクティスです。レスポンスボディには、作成されたリソースの完全な表現を含めることが推奨されます。

Locationヘッダー: /orders/ORD12345/lines/2

レスポンスボディ (JSON例):

{
  "orderId": "ORD12345",
  "lineNumber": 2,
  "item": "Mouse",
  "quantity": 2,
  "links": [
    {
      "rel": "self",
      "href": "/orders/ORD12345/lines/2"
    },
    {
      "rel": "order",
      "href": "/orders/ORD12345"
    }
  ]
}

データの更新 (PUT / PATCH)

特定のリソースを更新する場合、識別子である複合キー全体をURLに含めて指定します。

PUT /orders/{orderId}/lines/{lineNumber} PATCH /orders/{orderId}/lines/{lineNumber}

PUTリクエストはリソースの完全な置換を意図するため、リクエストボディには更新後のリソースの完全な状態(ただし、複合キー要素を除くのが一般的)を含めます。PATCHリクエストはリソースの部分的な更新を意図するため、リクエストボディには変更したいプロパティのみを含めます。

PUTリクエストボディ (JSON例):

{
  "item": "Wireless Mouse",
  "quantity": 2
}

orderIdlineNumber はURLで指定済みのためボディには含めない)

PATCHリクエストボディ (JSON例):

{
  "item": "Wireless Mouse"
}

レスポンス:

成功時には 200 OK または 204 No Content を返します。200 OK の場合は、更新後のリソースの表現をレスポンスボディに含めることが一般的です。

データの削除 (DELETE)

特定のリソースを削除する場合も、識別子である複合キー全体をURLに含めて指定します。

DELETE /orders/{orderId}/lines/{lineNumber}

リクエストボディは通常不要です。

レスポンス:

成功時には 200 OK (ボディ付き), 202 Accepted, または 204 No Content (ボディなし) を返します。リソースが完全に削除された場合は 204 No Content が適切です。

考慮事項

まとめ

RESTful APIで複合キーを持つリソースを扱う場合、最もRESTfulな設計原則に沿ったアプローチは、複合キーを構成する要素をURLのパスパラメータとして表現し、リソースを一意に識別することです。特に階層構造を持つデータに適しています。

複合キーを持つデータのRESTfulなモデリングは、単一キーの場合と比較して考慮すべき点が増えますが、本記事で解説したパターンと考慮事項を参考に、分かりやすく保守性の高いAPI設計を目指してください。