RESTful APIのデータモデリング:データベース正規化レベルと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と数量だけ含めるなど ...
]
}
メリット:
- データが重複しないため、更新時の不整合を防ぎやすい(API側での更新がある場合)。
- ペイロードサイズが小さくなる可能性がある。
- リソース間の関係性が明確になる。
- データベース構造とのマッピングが比較的容易。
デメリット:
- 関連データの取得に追加のAPIコールが必要になる場合が多い(N+1問題)。
- クライアント側で複数のAPIレスポンスを組み合わせて表示するロジックが必要になる。
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": [
// ... 注文商品アイテムの詳細(商品名、単価なども含む)を配列で埋め込み ...
]
}
メリット:
- 1回のAPIコールで必要なデータがまとめて取得できるため、クライアント側の実装がシンプルになり、APIコールの回数を削減できる。
- 特にモバイルアプリケーションなど、APIコール回数を減らしたい場合に有効。
- 表示系のAPI(Read操作が主)に適している。
デメリット:
- データが重複する可能性があり、API側で更新(特に顧客情報など、埋め込まれている参照先のデータ)がある場合は、整合性を保つための考慮が必要になる。
- ペイロードサイズが大きくなる可能性がある。
- 関連データの一部のみが必要な場合でも、全ての埋め込みデータが返されるため、帯域幅の無駄になる可能性がある。
- 後方互換性を保ちながらペイロード構造を変更するのが難しくなる場合がある。
設計判断の基準と考慮事項
どちらのアプローチを採用するかは、トレードオフを考慮して決定します。以下の点を考慮すると良いでしょう。
- 利用シーン: そのAPIが主にどのような目的で使われるか。一覧表示で関連情報が必須か、詳細表示でたまに関連情報が必要になるかなど。
- パフォーマンス: APIコール回数を減らすことが重要か、ペイロードサイズを抑えることが重要か。
- クライアント側の開発容易性: クライアント開発者が複数のAPIコールを管理し、データを組み合わせるのが容易か、それとも単一のレスポンスで済ませたいか。
- データの更新頻度: 埋め込むデータが頻繁に更新されるかどうか。更新される場合、埋め込みデータが古くなるリスクをどう扱うか。
- APIの保守性: ペイロード構造の変更頻度や、変更時の影響範囲。非正規化は変更が難しくなる傾向があります。
- データの量: 埋め込むデータ量が非常に多い場合、ペイロードが肥大化しすぎるリスクがあります。
多くの場合、完全に正規化または非正規化に偏るのではなく、ハイブリッドなアプローチを取ることが現実的です。例えば、頻繁に一緒に表示される少量の関連データ(例: 顧客名、商品名、合計金額など)は埋め込み、それ以外(例: 顧客の住所詳細、商品の詳細説明など)はIDやリンクで参照させる、といった設計です。
また、APIによっては、クエリパラメータなどでクライアント側が必要なフィールドや関連リソースを指定できるようにする、いわゆる「フィールド選択(Field Selection)」や「関連リソースのインクルード(Resource Inclusion)」の機能を提供することで、柔軟性を持たせることも可能です。例えば、/orders/{id}?include=customer,items
のように指定すると顧客情報と商品詳細を埋め込んで返す、といった設計です。これはGraphQLのようなアプローチにも近い考え方と言えます。
アンチパターン
- 過度な非正規化: 多くの関連データを闇雲に埋め込みすぎると、ペイロードが非常に大きくなり、不要なデータを常に取得することになりパフォーマンスを悪化させます。また、データ重複が増え、管理が複雑になります。
- 過度な正規化: データベーススキーマをそのままAPIにするだけで、API利用者は必要な情報を得るために毎回複数のAPIコールを発行しなければならない状態になります。これにより、API利用側の開発コストが増加し、N+1問題によるサーバー負荷増大も招きかねません。
まとめ
RESTful APIのデータモデリングにおいて、データベースの正規化レベルは重要な出発点となりますが、それをそのままAPIリソースに適用するのではなく、APIの利用目的とクライアント側の利便性を最優先に考えることが重要です。
関連データをどこまで一つのリソースに含めるか(非正規化するか)は、パフォーマンス、開発容易性、保守性などのトレードオフを考慮して慎重に判断する必要があります。多くの場合、ハイブリッドなアプローチや、クライアントからのデータ選択を可能にする機能の提供が有効な解決策となります。
「なぜこのデータをこのリソースに含めるのか?」「クライアントはこのデータを使って何をしたいのか?」といった問いを常に持ち、利用シーンに最適なデータ構造を設計していくことが、使いやすく、保守性の高いAPIへの鍵となります。