RESTful API データモデリング

RESTful APIで関連リソースをまとめて作成・更新するデータモデリング

Tags: RESTful API, データモデリング, リソース設計, 関連リソース, 一括操作

はじめに

RESTful APIの設計において、データモデリングは非常に重要な要素です。単一のリソースを操作するAPI(例えば、特定のユーザー情報を取得する GET /users/{id} や、単一の商品情報を作成する POST /products など)の設計は比較的 straightforward です。しかし、システムを構築していく中で、関連する複数のリソースを一度にまとめて作成したり更新したりしたい、という要求が出てくることは少なくありません。

例えば、

このようなケースでは、単一リソース操作のAPIを複数回呼び出す方法も考えられますが、クライアント側の実装が複雑になったり、ネットワークの負荷が増えたり、トランザクション性が失われたりといった問題が発生する可能性があります。

この記事では、RESTful APIで関連するリソースをまとめて作成・更新する際のデータモデリングに焦点を当て、その設計の考え方、具体的なパターン、そして設計上の注意点について解説します。

なぜ関連リソースをまとめて操作したいのか?

関連リソースをまとめて操作したいという要求の背景には、主に以下の理由があります。

  1. 効率性: クライアントがサーバーと何度も通信することなく、一度のリクエストで必要な操作を完了できるため、APIコールの回数を減らし、レスポンス時間を短縮できます。
  2. トランザクション性: 関連する一連の操作(例: 注文本体の作成と注文明細の作成)をアトミック(不可分)な処理として扱いたい場合、サーバー側でまとめて処理することで、一部だけが成功して不整合が発生するリスクを減らせます。
  3. UI/UXへの対応: ユーザーインターフェース上で複数の項目が関連付けられており、それらを一つのフォームとして入力・送信する場合など、UIの操作感に合わせてAPIも設計したいという要求があります。

設計上の課題

関連リソースをまとめて操作するAPIを設計する際には、いくつかの課題が生じます。

これらの課題を解決するために、データモデリングの視点から、APIのリクエスト・レスポンスの構造を設計していくことが重要です。

具体的な設計パターン

関連リソースをまとめて作成・更新するためのデータモデリングには、いくつかのパターンが考えられます。ここでは代表的なものを紹介します。

パターン1: 親リソースの操作としてネストした子リソースを含める

これは、作成または更新対象となる中心的なリソース(親リソース)の表現の中に、関連するリソース(子リソース)のリストをネストして含めるパターンです。特に、親リソースと子リソースが強い親子関係にあり、子リソースが親リソースなしでは独立して存在しないような場合に適しています(例: 注文と注文明細、プロジェクトとタスク)。

作成操作 (POST)

新しい親リソースを作成する際に、同時に初期の子リソースリストも作成します。

{
  "name": "新しいプロジェクト",
  "description": "今年の目標達成プロジェクト",
  "tasks": [
    {
      "description": "要件定義をまとめる",
      "status": "todo",
      "due_date": "2023-11-15"
    },
    {
      "description": "設計を進める",
      "status": "todo",
      "due_date": "2023-11-30"
    }
  ]
}

サーバーは、このリクエストを受け取り、新しいプロジェクトとそのプロジェクトに関連付けられたタスクをアトミックに(まとめて)作成します。

更新操作 (PATCHまたはPUT)

既存の親リソースを更新する際に、関連する子リソースのリスト全体を更新または置き換えるように設計します。PATCHを使う場合は、差分更新のセマンティクスを定義します。リストの更新では、以下のような操作が考えられます。

PATCHリクエストでこれらの操作をどのように表現するかは重要な設計判断です。一般的なアプローチとしては、以下のような方法があります。

{
  "name": "更新されたプロジェクト名",
  "tasks": [
    {
      "id": "task-abc", // 既存タスクのID
      "description": "要件定義(修正)",
      "status": "in_progress"
    },
    {
      "id": "task-xyz", // 既存タスクのID
      "status": "done" // ステータスのみ更新
    },
    {
      // 新規タスクのためIDなし
      "description": "テスト計画を作成する",
      "status": "todo",
      "due_date": "2023-12-10"
    }
    // ここにID "task-123" のタスクが含まれていなければ、サーバー側で削除する
  ]
}

このパターンのメリット:

このパターンのデメリット:

パターン2: 専用の複合リソースを作成・更新する

これは、複数の独立した、あるいは緩やかに結合したリソースに対する操作を、一つの「複合リソース」としてモデル化するパターンです。パターン1が強い親子関係に適しているのに対し、このパターンは複数の異なる種類のリソースをまとめて扱いたい場合や、操作そのものをリソースとして捉えたい場合に有効なことがあります。

{
  "operations": [
    {
      "method": "POST",
      "path": "/projects",
      "body": {
        "name": "プロジェクトA",
        "description": "詳細"
      }
    },
    {
      "method": "POST",
      "path": "/tasks",
      "body": {
        "project_id": "(前の操作で作成されたプロジェクトのID)",
        "description": "タスク1",
        "status": "todo"
      }
    },
    {
      "method": "PATCH",
      "path": "/users/user-id-1",
      "body": {
        "status": "active"
      }
    }
  ]
}

この例はやや汎用的なバッチ処理の形式ですが、より特定のユースケースに特化した複合リソースとして設計することも可能です。例えば、「プロジェクトと関連タスクの作成」という操作そのものを一つのリソースとして定義し、そのリソースに対してPOSTする、といった考え方です。

{
  "project_details": {
    "name": "新しいプロジェクト",
    "description": "詳細"
  },
  "initial_tasks": [
    {
      "description": "タスクA",
      "status": "todo"
    },
    {
      "description": "タスクB",
      "status": "todo"
    }
  ]
}

このリクエストを受け取ったサーバーは、project_detailsinitial_tasks の情報を使って、プロジェクトとタスクを関連付けて作成します。

このパターンのメリット:

このパターンのデメリット:

パターン3: アクションとしてモデル化する

厳密にはデータモデリングというよりはエンドポイント設計の側面が強いですが、RESTful原則(リソース指向)にこだわりすぎず、特定のビジネスオペレーションを表現するために、動詞を含むURIや、リソースの特定の状態遷移を表すURIを使うことがあります。

{
  "tasks_to_add": [
    {
      "description": "追加タスク1",
      "status": "todo"
    },
    {
      "description": "追加タスク2",
      "status": "todo"
    }
  ]
}

このパターンは、リストの置き換えや差分更新ではなく、「既存のプロジェクトにタスクを『追加する』」という特定の操作を表現する場合に有効です。

このパターンのメリット:

このパターンのデメリット:

どのパターンを選択するかは、操作の性質、リソース間の関係性、システム全体の設計思想などを考慮して判断する必要があります。強い親子関係で子リソースが親なしで存在しえない場合はパターン1が自然ですし、複数の既存リソースに対して横断的な処理を行いたい場合はパターン2や、ユースケースによってはパターン3が適する場合もあります。

アンチパターン

関連リソースをまとめて操作しようとして陥りがちなアンチパターンも存在します。

これらのアンチパターンは、リソース指向から離れ、RPC(Remote Procedure Call)的な考え方に偏りすぎている場合に発生しやすいため注意が必要です。

考慮事項と実践的なヒント

関連リソースをまとめて操作するAPIを設計・実装する際には、以下の点を考慮すると良いでしょう。

まとめ

RESTful APIで関連リソースをまとめて作成・更新するデータモデリングは、APIの効率性、トランザクション性、そしてクライアント開発の容易性に大きく影響します。単一リソースの操作APIを組み合わせるだけでなく、関連するデータを一つのまとまりとして表現し、APIで操作できるように設計することは、現実世界の複雑なビジネスロジックをAPIにマッピングする上で避けられない課題です。

この記事で紹介した、親リソース操作としてのネスト構造、専用複合リソース、あるいはアクションとしてのモデル化といったパターンは、それぞれ異なるユースケースや設計思想に適しています。どのパターンを選択するかに正解は一つではありませんが、操作の性質、リソース間の関係性、そしてシステム全体の設計原則を考慮し、最も適切で保守性の高い設計を選択することが重要です。

設計を進めるにあたっては、リクエスト・レスポンスの具体的なJSON構造を定義し、それが意図する操作を明確にし、エラーハンドリングや冪等性といった非機能要件も合わせて考慮に入れることが、堅牢で使いやすいAPIを構築するための鍵となります。そして、その設計をOpenAPIなどで正確にドキュメント化することで、APIは開発チーム内外で共有される「契約」として、より高い価値を発揮するでしょう。