RESTful API データモデリング

RESTful APIでリソースのバージョン履歴を扱うデータモデリング

Tags: データモデリング, RESTful API, バージョン管理, リソース設計, API設計

はじめに

多くのシステムでは、データが時間と共に変化し、過去の状態を追跡したり、特定の時点のデータを確認したりする必要が生じます。文書管理システムにおけるドキュメントの改訂履歴や、設定管理システムにおける設定ファイルの変更履歴などがその代表例です。RESTful APIを設計する上で、これらの「リソースのバージョン履歴」をどのようにデータモデリングし、APIとして提供するかは重要な課題となります。

単に最新の状態を取得するだけでなく、特定の過去バージョンを取得したり、バージョン間の差分を確認したり、新しいバージョンを作成したりといった操作が必要になることがあります。これらの要件を満たすAPI設計は、クライアントがデータを正確に扱い、変更を管理するために不可欠です。

この記事では、RESTful APIでリソースのバージョン履歴を扱うためのデータモデリングのアプローチ、APIエンドポイントの設計パターン、具体的なリソース表現、そして設計における考慮事項について解説します。

リソースのバージョン履歴を扱うことの課題

リソースのバージョン履歴をAPIとして提供する場合、いくつかの設計上の課題があります。

  1. 過去バージョンの取得: 特定のリソースについて、最新ではない過去のバージョンをどのように指定し、取得できるようにするか。
  2. バージョンリストの提供: あるリソースの利用可能なバージョン一覧をどのように取得できるようにするか。
  3. 新しいバージョンの作成: 既存のリソースを編集して新しいバージョンとして保存する場合、どのようなAPI操作で実現するか。
  4. バージョン間の差分: 異なるバージョン間の変更内容(差分)をどのように取得できるようにするか。
  5. データ量の管理: リソースのバージョンが増えるにつれて、データ量が増大し、パフォーマンスへの影響が生じうる。これをどのように考慮するか。

これらの課題に対して、RESTfulの原則に基づきつつ、利用しやすく効率的なデータモデリングとAPI設計が求められます。

データモデリングの基本的な考え方

リソースのバージョン履歴をデータとしてモデル化する際、最も一般的なアプローチは、バージョンを元のリソースとは別のエンティティとして扱う、あるいは元のリソースにバージョン関連の情報を持たせることです。

例えば、「Document」というリソースがあり、そのバージョン履歴を管理する場合を考えます。

アプローチ1: バージョンをサブリソースとしてモデリングする

これは、バージョン履歴を元のリソース(Document)のサブリソースとして定義する考え方です。各バージョンは独立したリソースとしての性質を持ちつつ、常に親リソース(Document)に紐づきます。

Version リソースは、そのバージョン自体の内容(例:ドキュメント本文)に加えて、そのバージョンに関するメタデータ(例:バージョン番号、作成日時、作成者、変更概要など)を持つことが考えられます。

データ構造のイメージ(JSON):

// Document リソースの例(最新バージョンや概要情報など)
{
  "id": "doc-123",
  "title": "プロジェクト計画書",
  "current_version": 5,
  "updated_at": "2023-10-27T10:00:00Z",
  // ...その他のDocument全体のメタデータ...
}

// Version リソースの例(Document doc-123 のバージョン 3)
{
  "id": "ver-456", // バージョン自体のユニークID
  "document_id": "doc-123", // 親Documentへの参照
  "version_number": 3,
  "content": "プロジェクトの初期計画内容...", // このバージョンの具体的な内容
  "created_at": "2023-09-15T09:00:00Z",
  "created_by": {"id": "user-abc", "name": "山田太郎"},
  "change_summary": "基本的な構成要素を定義"
}

このアプローチでは、元のリソースとバージョンという概念を明確に分離し、関連付けます。

アプローチ2: リソース自身にバージョン情報と内容を持たせる

このアプローチでは、各バージョンが独立したリソースとして存在し、それぞれのインスタンスがそのバージョンに対応する内容とメタデータを持ちます。この場合、特定の「最新バージョン」を示すポインタのようなものが別途必要になるかもしれません。

データ構造のイメージ(JSON):

// DocumentVersion リソースの例(バージョンを独立したリソースとして扱う)
{
  "id": "ver-456", // このバージョンインスタンスのユニークID
  "document_id": "doc-123", // 元のDocumentを識別するID
  "version_number": 3,
  "content": "プロジェクトの初期計画内容...", // このバージョンの具体的な内容
  "created_at": "2023-09-15T09:00:00Z",
  "created_by": {"id": "user-abc", "name": "山田太郎"},
  "change_summary": "基本的な構成要素を定義"
}

そして、別に「最新バージョンがこれである」という情報を持つエンティティや、最新バージョンを取得するための別のリソース識別方法を設けることが考えられます。

RESTfulの文脈では、アプローチ1のように、バージョンを親リソースのサブリソースとして扱う方が、親リソースとバージョン履歴の関係性が直感的に理解しやすく、APIのパス設計もしやすいため推奨されることが多いです。以下では、このサブリソースモデルを前提にAPI設計を進めます。

APIエンドポイントの設計パターン

サブリソースモデルを採用した場合、APIエンドポイントは以下のようなパターンで設計することが考えられます。

1. 特定リソースの最新バージョンを取得

最も一般的な操作です。リソースのIDのみで最新バージョンを取得できるようにします。

GET /documents/{documentId}

レスポンス例: 最新バージョンの内容と、それがどのバージョンであるかを示す情報を含めるのが親切です。

{
  "id": "doc-123",
  "title": "プロジェクト計画書",
  "version_number": 5, // 最新のバージョン番号
  "content": "最新の計画内容...", // 最新バージョンの内容
  "created_at": "2023-10-27T10:00:00Z", // 最新バージョンの作成日時
  "created_by": {"id": "user-def", "name": "田中花子"}, // 最新バージョンの作成者
  "change_summary": "最終調整"
}

注意点として、ここで返されるリソース構造は、バージョン一覧や特定バージョン取得のレスポンス構造と一部重複しますが、目的が「最新を取得する」ことにあるため、内容(content)を含めるのが一般的です。

2. 特定リソースのバージョン一覧を取得

あるリソースについて、利用可能なすべてのバージョンまたは一部のバージョンのリストを取得します。

GET /documents/{documentId}/versions

レスポンス例: バージョンごとのメタ情報のリストを返します。リストには内容(content)は含めず、バージョン番号、作成日時、作成者、変更概要などのサマリー情報のみを含めるのが一般的です。

[
  {
    "id": "ver-abc",
    "version_number": 1,
    "created_at": "2023-09-01T09:00:00Z",
    "created_by": {"id": "user-abc", "name": "山田太郎"},
    "change_summary": "初回作成"
  },
  {
    "id": "ver-def",
    "version_number": 2,
    "created_at": "2023-09-10T14:30:00Z",
    "created_by": {"id": "user-abc", "name": "山田太郎"},
    "change_summary": "セクション追加"
  },
  // ...他のバージョン...
  {
    "id": "ver-xyz",
    "version_number": 5,
    "created_at": "2023-10-27T10:00:00Z",
    "created_by": {"id": "user-def", "name": "田中花子"},
    "change_summary": "最終調整"
  }
]

大量のバージョンがある場合は、ページネーション (?page=...&size=...) やフィルタリング (?created_by=...)、ソート (?sort=version_number,desc) などのクエリパラメータをサポートすることが望ましいです。

3. 特定の過去バージョンを取得

リソースのIDとバージョン指定子(バージョン番号など)を用いて、特定の過去バージョンを取得します。バージョン指定子には、連番のバージョン番号、UUID、タイムスタンプなどを使用することが考えられます。ここではバージョン番号を使用する例を示します。

GET /documents/{documentId}/versions/{versionNumber}

例: GET /documents/doc-123/versions/3

レスポンス例: 指定されたバージョンの内容とメタ情報を含みます。

{
  "id": "ver-456", // このバージョンインスタンスのID
  "document_id": "doc-123", // 親Document ID
  "version_number": 3,
  "content": "プロジェクトの初期計画内容...", // このバージョンの具体的な内容
  "created_at": "2023-09-15T09:00:00Z",
  "created_by": {"id": "user-abc", "name": "山田太郎"},
  "change_summary": "基本的な構成要素を定義"
}

ここではパスパラメータ {versionNumber} でバージョンを指定していますが、クエリパラメータ (/documents/{documentId}?version={versionNumber}) で指定する方法も考えられます。パスパラメータはリソースの特定バージョンを独立したリソースとして表現する意図が強い場合に適しており、クエリパラメータはあくまで特定の時点の表現を「取得」するためのフィルタリングとして使用する意図が強い場合に適していると言えます。どちらの設計が良いかは、そのAPIがバージョンをどの程度独立したエンティティとして扱うかに依存します。サブリソースモデルを採用しているため、パスパラメータの方がよりRESTfulな表現に沿っていると考えられます。

4. 新しいバージョンを作成

既存のリソースの内容を更新し、それを新しいバージョンとして保存する操作です。これは、元のリソースに対してPOSTメソッドを使用し、リクエストボディに新しい内容と変更概要を含めることで表現できます。

POST /documents/{documentId}/versions

リクエストボディ例:

{
  "content": "修正された計画内容...", // 新しいバージョンの内容
  "change_summary": "期日を修正" // このバージョンの変更概要
}

レスポンス例: 成功した場合、新しく作成されたバージョンの情報(ID, バージョン番号, 作成日時など)を返します。201 Created ステータスコードと共に、Location ヘッダーに新しいバージョンリソースのURI (/documents/doc-123/versions/6) を含めるのが標準的なRESTfulな応答です。

{
  "id": "ver-newly-created",
  "document_id": "doc-123",
  "version_number": 6,
  "created_at": "2023-10-28T11:00:00Z",
  "created_by": {"id": "user-self", "name": "現在のユーザー"},
  "change_summary": "期日を修正"
}

5. バージョン間の差分を取得

特定の2つのバージョン間の差分を取得するAPIエンドポイントです。

GET /documents/{documentId}/versions/{version1}/diff/{version2}

例: GET /documents/doc-123/versions/3/diff/5 (バージョン3からバージョン5への差分)

レスポンス例: 差分の表現方法は様々ですが、テキストベースの差分であればUnified Diff形式などが考えられます。構造化データ(JSONなど)の差分であれば、JSON Patchや独自の差分形式を用いることも可能です。

--- a/document-v3.json
+++ b/document-v5.json
@@ -1,6 +1,6 @@
 {
   "id": "doc-123",
-  "title": "プロジェクト計画書",
+  "title": "改訂プロジェクト計画書",
   "version_number": 3,
   "content": "プロジェクトの初期計画内容...",
   "created_at": "2023-09-15T09:00:00Z",

JSON Patch形式の例(もしリソース内容がJSONであれば):

[
  { "op": "replace", "path": "/title", "value": "改訂プロジェクト計画書" },
  { "op": "add", "path": "/status", "value": "Approved" }
]

どのような差分形式が適切かは、対象となるリソースの内容やクライアント側の処理要件に依存します。

考慮事項

アンチパターン

まとめ

リソースのバージョン履歴をRESTful APIで扱うためには、バージョンを親リソースのサブリソースとしてデータモデリングし、対応するAPIエンドポイント(一覧取得、特定バージョン取得、新規バージョン作成、差分取得など)を適切に設計することが有効です。

バージョン履歴のモデリングは、データの変更可能性を適切に管理し、システムの監査性や復元性を高める上で重要な要素です。この記事で紹介したパターンや考慮事項が、保守性が高く、利用しやすいAPI設計の一助となれば幸いです。