RESTful API データモデリング

RESTful APIで状態を表現するデータモデリング:ステータス管理と設計パターン

Tags: API設計, データモデリング, RESTful API, 状態管理, 設計パターン

はじめに

多くの業務システムにおいて、データは時間の経過とともに様々な状態を変化させます。例えば、ECサイトの「注文」は「保留中」から「処理中」、「発送済み」、「完了」、「キャンセル」といった状態を経てライフサイクルを終えます。このような「状態を持つリソース」をRESTful APIでどのように表現し、モデリングするかは、APIの使いやすさ、保守性、そしてシステムの整合性を大きく左右します。

不適切な状態モデリングは、クライアント側に複雑な状態管理ロジックを強いたり、APIの変更が困難になったりする原因となります。本記事では、RESTful APIで状態を持つリソースを適切にモデリングするための基本的な考え方と、具体的な設計パターンについて解説します。

状態を持つリソースとは何か

「状態を持つリソース」とは、その属性値や許可される操作が、内部的な状態によって変化するリソースのことです。前述の注文リソースのように、状態が進むにつれてデータ構造の一部(例: 発送日が追加される)や、実行できるアクション(例: キャンセルできるのは特定の状態のときだけ)が変わります。

これらの状態は、システム内のビジネスロジックやワークフローに基づいて定義されます。API設計者は、この状態と状態遷移をどのようにリソース表現に落とし込み、クライアントに伝えるかを検討する必要があります。

APIにおける状態表現の基本的な考え方

RESTful APIでは、リソースの状態はリソースの「表現(Representation)」の一部としてクライアントに提示されるのが基本です。クライアントはリソースの表現に含まれる状態情報を読み取り、それに基づいてユーザーインターフェースを変化させたり、次に可能なアクションを判断したりします。

状態を表現する最も一般的な方法は、リソースのデータ構造に専用のフィールドを持たせることです。

具体的なデータモデリングパターン

状態を表現するための具体的なデータモデリングパターンをいくつか紹介します。

パターン1: 単一のステータスフィールド

最もシンプルで一般的なパターンです。リソースのトップレベルに、現在の状態を示す文字列フィールドを持たせます。

{
  "id": "order-123",
  "customer_id": "cust-456",
  "amount": 5000,
  "status": "pending", // 状態を示す文字列フィールド
  "created_at": "2023-10-26T10:00:00Z"
}

注文が処理中に変わった場合:

{
  "id": "order-123",
  "customer_id": "cust-456",
  "amount": 5000,
  "status": "processing",
  "created_at": "2023-10-26T10:00:00Z"
}

パターン2: 状態に関連する追加情報を含む

リソースの状態によって、レスポンスに含めるべき情報が変わる場合があります。例えば、注文が「発送済み」になったら発送日や追跡番号が必要になります。

この場合、状態を示すフィールドはそのままに、状態に関連する情報を追加のフィールドとしてレスポンスに含める方法が考えられます。

方法A: 状態に関わらず全フィールドを返す(関連情報はnullの場合あり)

{
  "id": "order-124",
  "customer_id": "cust-789",
  "amount": 8000,
  "status": "processing",
  "created_at": "2023-10-26T11:00:00Z",
  "shipped_at": null,      // 処理中なのでまだnull
  "tracking_number": null, // 処理中なのでまだnull
  "delivered_at": null
}

注文が「発送済み」になった場合:

{
  "id": "order-124",
  "customer_id": "cust-789",
  "amount": 8000,
  "status": "shipped",
  "created_at": "2023-10-26T11:00:00Z",
  "shipped_at": "2023-10-27T09:00:00Z", // 発送日が入る
  "tracking_number": "TRK123456789", // 追跡番号が入る
  "delivered_at": null
}

方法B: 状態に応じて動的にフィールドを追加・削除する

状態によって存在するフィールドを変える方法です。

注文が「処理中」の場合(方法Aと同じ):

{
  "id": "order-124",
  "customer_id": "cust-789",
  "amount": 8000,
  "status": "processing",
  "created_at": "2023-10-26T11:00:00Z"
  // shipped_at, tracking_number フィールドは存在しない
}

注文が「発送済み」になった場合:

{
  "id": "order-124",
  "customer_id": "cust-789",
  "amount": 8000,
  "status": "shipped",
  "created_at": "2023-10-26T11:00:00Z",
  "shipped_at": "2023-10-27T09:00:00Z",
  "tracking_number": "TRK123456789"
}

一般的には、方法Aのようにフィールドを固定し、関連情報がない場合はnullとする方が、クライアントの実装コストを下げ、APIの安定性(後方互換性)を保ちやすい傾向があります。ただし、フィールド数が膨大になる場合は方法Bも検討価値があります。

パターン3: ネストされた状態オブジェクト

状態自体がより複雑な構造を持つ場合や、状態に関するメタデータ(いつ、誰が状態を変更したかなど)を保持したい場合に有効です。

{
  "id": "order-125",
  "customer_id": "cust-101",
  "amount": 10000,
  "state": { // 状態を表現するネストされたオブジェクト
    "status": "shipped",
    "updated_at": "2023-10-27T09:00:00Z",
    "updated_by": "user-abc",
    "shipping_info": { // 状態に紐づく詳細情報もネスト可能
      "shipped_at": "2023-10-27T09:00:00Z",
      "tracking_number": "TRK987654321"
    }
  },
  "created_at": "2023-10-26T12:00:00Z"
}

どのパターンを選択するかは、リソースの状態の複雑さ、状態に関連する情報の多さ、必要となるメタデータの有無などによって判断します。多くの場合は「パターン1 + パターン2(方法A)」の組み合わせで十分対応できるでしょう。

状態遷移の設計

リソースの状態をクライアントが変更できるようにするには、いくつかの設計方法があります。重要なのは、状態遷移のビジネスロジック(例: 支払いが完了していない注文は発送済みにできない)はサーバー側で厳格に管理することです。

方法A: 特定のアクション用エンドポイント

状態遷移をトリガーするアクションに対して、専用のエンドポイントを用意する方法です。

これらのエンドポイントに対するリクエストボディには、アクションに必要な情報を含めます。例えば、発送アクションなら追跡番号や運送会社情報などです。

POST /orders/order-123/ship HTTP/1.1
Content-Type: application/json

{
  "tracking_number": "TRK123456789",
  "carrier": "Yamato Transport"
}

サーバー側では、現在の注文の状態を確認し、このアクションが許可されているか、必要な情報が揃っているかなどを検証します。成功すれば状態を更新し、更新後のリソース表現を返すのが一般的です(レスポンスコード 200 OK や 202 Accepted など)。

方法B: リソース更新(PATCH/PUT)による状態変更

リソース全体または一部を更新する標準的なエンドポイント(PUT /orders/{id}PATCH /orders/{id}) を利用して、ステータスフィールドを更新する方法です。

PATCH /orders/order-123 HTTP/1.1
Content-Type: application/json

{
  "status": "cancelled",
  "reason": "Customer requested cancellation"
}

この場合も、サーバー側では現在の状態を確認し、指定された新しい状態への遷移が現在の状態から許可されているか(例: 既に発送済みの注文をキャンセルしようとしていないか)、リクエストに含まれる情報が適切かなどを検証する必要があります。

一般的には、状態遷移が明確なアクションであり、遷移に際して特定のビジネスロジックや追加情報が必要な場合は、方法A(アクション用エンドポイント)の方がAPIの意図が明確になり、サーバー側の実装も整理しやすいため推奨される傾向があります。単純な「アクティブ/非アクティブ」のような二値の状態変更であれば、方法Bも十分に考えられます。

アンチパターン

状態モデリングにおいて避けるべきアンチパターンをいくつか挙げます。

その他の考慮事項

まとめ

RESTful APIにおける状態を持つリソースのモデリングは、APIの品質に大きく影響します。状態はリソース表現の一部として明確に含め、可能であれば意味のある文字列フィールドを使用します。状態によってデータ構造が変わる場合は、一貫性を保ちつつ必要な情報を追加する方法を検討します。

状態遷移をトリガーするアクションは、専用のエンドポイントでサーバー側が制御するのが望ましいアプローチです。状態コードのマジックナンバーや、状態による極端なレスポンス構造の変化といったアンチパターンは避け、クライアントが扱いやすく、サーバー側で整合性を保証できる設計を目指すことが重要です。

適切な状態モデリングを行うことで、保守性が高く、理解しやすいAPIを構築することができます。