RESTful API データモデリング

RESTful APIのデータモデリング:データベース正規化レベルとAPIリソースの関係性を考える

Tags: データモデリング, API設計, 正規化, 非正規化, レスポンス設計

はじめに

RESTful APIを設計する際、データモデリングは非常に重要な工程です。特に、データの構造をどのようにAPIリソースとして表現するかは、APIの使いやすさ、パフォーマンス、そして保守性に大きく影響します。多くのAPIは背後にあるデータベースのデータを扱いますが、データベース設計における正規化の考え方が、そのままAPIリソースの設計に適用できるとは限りません。

本記事では、データベースの正規化の概念を振り返りつつ、それを踏まえてRESTful APIのリソース設計においてどのようなデータモデリングの判断が必要になるのか、具体的な例を交えながら解説します。

データベースの正規化とは

まず、データベースの正規化について簡単に触れておきます。正規化とは、リレーションシップデータベースにおいて、データの冗長性を排除し、一貫性を保つための設計手法です。データの依存関係を整理し、更新時異状(挿入、削除、更新時に発生する不整合)を防ぐことを目的とします。正規化のレベルは第一正規形(1NF)から第五正規形(5NF)、さらにはドメインキー正規形(DKNF)までありますが、一般的には第三正規形(3NF)やボイスコッド正規形(BCNF)までを目指すことが多いです。

正規化されたデータベーススキーマは、データの整合性を高く保ち、ストレージ容量を節約できるというメリットがあります。しかし、データを取得する際には複数のテーブルを結合(JOIN)する必要が生じ、複雑なクエリやパフォーマンスのボトルネックの原因となる可能性もあります。

APIリソースにおけるデータモデリングの課題

データベースが正規化されている場合、そのスキーマ構造をそのままAPIリソースとして公開すべきでしょうか。例えば、「注文」と「顧客」というデータがあるとします。データベースでは通常、orders テーブルと customers テーブルに分けられ、orders テーブルが customer_id という外部キーで customers テーブルを参照するという正規化された構造になっています。

この構造をそのままAPIに反映すると、注文情報を取得する /orders/{id} エンドポイントは、顧客IDのみを返し、顧客の詳細情報を取得するには別途 /customers/{customer_id} というエンドポイントを呼び出す必要が出てきます。

これは一見、データベース構造を素直に反映しており理にかなっているように思えますが、APIの利用シーンによっては不都合が生じることがあります。例えば、注文リストを表示する画面では、各注文の情報を表示する際にその注文を行った顧客の名前も一緒に表示したい、という要求はよくあります。この場合、注文リストを取得するAPI /orders を呼び出した後、リスト内の各注文に対して個別に /customers/{customer_id} を呼び出す必要があり、N+1問題のような複数のAPIコールが発生してしまいます。

このように、データベースの正規化レベルをそのままAPIリソースに適用することが、必ずしもAPIの利用者にとって効率的で使いやすい設計になるとは限りません。

APIリソースのデータモデリング:正規化と非正規化の考慮

APIリソースを設計する際には、データベースの正規化レベルを考慮しつつも、APIの利用シーンクライアント側の開発容易性を重視したデータモデリングを行う必要があります。ここで「非正規化」の考え方がAPI側で有効になる場合があります。

APIリソースにおける非正規化とは、関連するデータを一つのリソースやレスポンスペイロードの中に含めて返す設計を指します。

1. 正規化されたAPIレスポンス

データベースの構造に近い、関連リソースへの参照(IDやリンク)を返す形式です。

例:注文リソース(正規化スタイル)

{
  "id": "order-123",
  "orderNumber": "ORD-001",
  "orderDate": "2023-10-27T10:00:00Z",
  "totalAmount": 150.00,
  "currency": "USD",
  "customer": {
    "id": "cust-abc",
    "links": [
      { "rel": "self", "href": "/customers/cust-abc" }
    ]
  },
  "items": [
    // ... 注文商品アイテムは別途 /orders/{id}/items で取得するか、ここにIDと数量だけ含めるなど ...
  ]
}

メリット:

デメリット:

2. 非正規化されたAPIレスポンス

関連するデータをリソースのペイロード内に埋め込んで返す形式です。

例:注文リソース(非正規化スタイル - 顧客情報を埋め込み)

{
  "id": "order-123",
  "orderNumber": "ORD-001",
  "orderDate": "2023-10-27T10:00:00Z",
  "totalAmount": 150.00,
  "currency": "USD",
  "customer": {
    "id": "cust-abc",
    "name": "山田 太郎",
    "email": "yamada.taro@example.com",
    "shippingAddress": {
        "street": "青山1-2-3",
        "city": "東京都",
        "country": "日本"
    }
    // ... 他の顧客情報 ...
  },
  "items": [
    // ... 注文商品アイテムの詳細(商品名、単価なども含む)を配列で埋め込み ...
  ]
}

メリット:

デメリット:

設計判断の基準と考慮事項

どちらのアプローチを採用するかは、トレードオフを考慮して決定します。以下の点を考慮すると良いでしょう。

多くの場合、完全に正規化または非正規化に偏るのではなく、ハイブリッドなアプローチを取ることが現実的です。例えば、頻繁に一緒に表示される少量の関連データ(例: 顧客名、商品名、合計金額など)は埋め込み、それ以外(例: 顧客の住所詳細、商品の詳細説明など)はIDやリンクで参照させる、といった設計です。

また、APIによっては、クエリパラメータなどでクライアント側が必要なフィールドや関連リソースを指定できるようにする、いわゆる「フィールド選択(Field Selection)」や「関連リソースのインクルード(Resource Inclusion)」の機能を提供することで、柔軟性を持たせることも可能です。例えば、/orders/{id}?include=customer,items のように指定すると顧客情報と商品詳細を埋め込んで返す、といった設計です。これはGraphQLのようなアプローチにも近い考え方と言えます。

アンチパターン

まとめ

RESTful APIのデータモデリングにおいて、データベースの正規化レベルは重要な出発点となりますが、それをそのままAPIリソースに適用するのではなく、APIの利用目的クライアント側の利便性を最優先に考えることが重要です。

関連データをどこまで一つのリソースに含めるか(非正規化するか)は、パフォーマンス、開発容易性、保守性などのトレードオフを考慮して慎重に判断する必要があります。多くの場合、ハイブリッドなアプローチや、クライアントからのデータ選択を可能にする機能の提供が有効な解決策となります。

「なぜこのデータをこのリソースに含めるのか?」「クライアントはこのデータを使って何をしたいのか?」といった問いを常に持ち、利用シーンに最適なデータ構造を設計していくことが、使いやすく、保守性の高いAPIへの鍵となります。