RESTful APIで「いいね」と「お気に入り」を扱うデータモデリング
はじめに
多くのウェブサービスやアプリケーションでは、ユーザーが特定のコンテンツやアイテムに対して興味や好意を示す「いいね」や「お気に入り」といった機能が実装されています。これらの機能は、ユーザーエンゲージメントを高め、サービス体験を向上させる上で非常に重要です。
APIを設計する際、これらの「いいね」や「お気に入り」といったユーザーのインタラクションデータをどのようにモデリングし、APIとして表現するかは一つの設計課題となります。単にデータベースに保存するだけでなく、RESTfulの原則に則り、クライアントにとって分かりやすく、かつ効率的で保守性の高いAPIを設計するためには、適切なデータモデリングの考え方が必要です。
この記事では、RESTful APIにおいて「いいね」や「お気に入り」の機能を実装する際のデータモデリングに焦点を当て、いくつかの設計パターンとそれぞれのメリット・デメリット、考慮すべき点について解説します。
「いいね」や「お気に入り」に関する設計上の課題
「いいね」や「お気に入り」のような機能は、一見シンプルに見えますが、API設計においてはいくつかの検討事項が存在します。
- リソースの定義: 「いいね」や「お気に入り」自体を独立したリソースとして扱うべきか、それとも対象となるリソース(記事、商品など)の一部として扱うべきか。
- 操作の表現: 「いいねする」「お気に入りに登録する」といった操作を、どのようなHTTPメソッドとURLで表現するか。
- 状態の表現: ユーザーが特定のアイテムを「いいね済み」「お気に入り済み」であるかという状態を、どのようにクライアントに伝えるか。
- 集計データの提供: 特定のアイテムに対する「いいね数」「お気に入り数」といった集計データをどのように提供するか。
- 重複操作の防止: 同じユーザーが同じアイテムに複数回「いいね」できないように、どのように設計するか。
これらの課題に対して、RESTfulの原則を意識したデータモデリングを考えることが重要です。
データモデリングの基本的な考え方
RESTful APIにおけるデータモデリングでは、システム内の主要な要素を「リソース」として識別することが基本です。「いいね」や「お気に入り」をリソースとして捉える場合、それは「あるユーザーが、あるアイテムに対して行ったポジティブな評価/ブックマーク」という事象を表現するリソースと考えることができます。
例えば、「記事」に対する「いいね」であれば、「記事」というリソースと「ユーザー」というリソースの間の「いいね」という関係性や事象を表すリソースとしてモデル化することが考えられます。
具体的な設計パターン
ここでは、「記事」に対する「いいね」機能を例にとり、いくつかのAPI設計パターンを提示します。
パターン1: 「いいね」を独立したリソースとして扱う
「いいね」という事象そのものを一つの独立したリソースとして定義するアプローチです。例えば /likes
というリソースコレクションを考えます。
-
URL設計:
POST /likes
: 新しい「いいね」を作成する(例: リクエストボディにどの記事に誰がいいねしたかの情報を含める)DELETE /likes/{like_id}
: 特定の「いいね」を取り消す
-
リクエスト/レスポンスの例:
-
記事に「いいね」する (POST /likes) ```json // リクエストボディ { "article_id": "article-abc-123", "user_id": "user-xyz-456" // 認証情報から取得するため不要な場合が多い }
// レスポンス (201 Created) { "id": "like-789-def", "article_id": "article-abc-123", "user_id": "user-xyz-456", "created_at": "2023-10-27T10:00:00Z" }
* **特定の「いいね」を取り消す (DELETE /likes/{like_id})**
http DELETE /likes/like-789-def HTTP/1.1 Host: api.example.com ``` レスポンス: 204 No Content
-
-
ユーザーがいいねした記事一覧を取得する:
GET /users/{user_id}/likes
: 特定のユーザーがいいねした「いいね」リソースのリストを取得。レスポンスには、いいね対象のリソース(記事)の情報を含むか、リンク(ID)を含む。json // レスポンス例 [ { "id": "like-789-def", "article_id": "article-abc-123", "user_id": "user-xyz-456", "created_at": "2023-10-27T10:00:00Z", "article": { // 対象リソースを埋め込み "id": "article-abc-123", "title": "記事タイトル", ... } }, ... ]
-
メリット:
- 「いいね」という事象自体を明確なリソースとして扱えるため、RESTfulの原則に則っている。
- 「いいね」に関する詳細情報(いつ、誰が)をリソースのプロパティとして管理しやすい。
- デメリット:
- 特定の記事に対する「いいね」操作が、記事リソースのURL (
/articles/{article_id}
) ではなく、/likes
という別のリソースコレクションに対して行われるため、直感的に分かりにくいと感じるクライアント開発者もいるかもしれない。 - ユーザーが特定記事を「いいね済みか」や「いいね数」を取得するために、別途APIコールが必要になる場合がある(後述の補足で解決可能)。
- 特定の記事に対する「いいね」操作が、記事リソースのURL (
パターン2: 対象リソースのサブコレクションとして扱う
「いいね」を、それが行われた対象リソース(この例では記事)のサブコレクションとして定義するアプローチです。
-
URL設計:
POST /articles/{article_id}/likes
: 特定の記事に「いいね」を作成するDELETE /articles/{article_id}/likes/{like_id}
: 特定の記事に対する特定の「いいね」を取り消すDELETE /articles/{article_id}/likes/@me
: 認証ユーザーが特定記事に対して行った「いいね」を取り消す(@me
は認証ユーザーを示す慣習的なID)
-
リクエスト/レスポンスの例:
-
記事に「いいね」する (POST /articles/{article_id}/likes)
http POST /articles/article-abc-123/likes HTTP/1.1 Host: api.example.com Authorization: Bearer <token_of_user_xyz_456>
リクエストボディは空、または認証ユーザーの情報などを含むこともあり得るが、認証情報を利用することが多い。json // レスポンス (201 Created) { "id": "like-789-def", // 作成されたいいねリソースのID "article_id": "article-abc-123", "user_id": "user-xyz-456", // 認証ユーザーのID "created_at": "2023-10-27T10:05:00Z" }
* 記事の「いいね」を取り消す (DELETE /articles/{article_id}/likes/@me)http DELETE /articles/article-abc-123/likes/@me HTTP/1.1 Host: api.example.com Authorization: Bearer <token_of_user_xyz_456>
レスポンス: 204 No Content
-
-
メリット:
- 操作の対象(記事)と操作の内容(いいね)がURL上で明確に関連付けられるため、クライアントにとって直感的で分かりやすい。
- 特定記事に対する「いいね」関連のエンドポイントが集約される。
- デメリット:
- ユーザーが「自分がいいねした全ての記事」のようなリストを取得する場合は、パターン1 (
GET /users/{user_id}/likes
) の方が自然なURL設計になる。パターン2では/users/{user_id}/articles?liked=true
のようなクエリパラメータで実現するなどの工夫が必要になる。
- ユーザーが「自分がいいねした全ての記事」のようなリストを取得する場合は、パターン1 (
パターン3: 対象リソースへのアクションとして扱う(RPCライクになりがちな設計)
RESTfulの原則からはやや外れる可能性もありますが、操作性を重視して対象リソースに直接アクションを紐づける形式です。
-
URL設計:
POST /articles/{article_id}/like
: 特定の記事に「いいね」するPOST /articles/{article_id}/unlike
: 特定の記事の「いいね」を取り消す
-
リクエスト/レスポンスの例:
-
記事に「いいね」する (POST /articles/{article_id}/like)
http POST /articles/article-abc-123/like HTTP/1.1 Host: api.example.com Authorization: Bearer <token_of_user_xyz_456>
レスポンス: 200 OK または 204 No Content -
記事の「いいね」を取り消す (POST /articles/{article_id}/unlike)
http POST /articles/article-abc-123/unlike HTTP/1.1 Host: api.example.com Authorization: Bearer <token_of_user_xyz_456>
レスポンス: 200 OK または 204 No Content
-
-
メリット:
- 操作名が明確で、クライアント開発者にとって意図が分かりやすい。
- URLがシンプルになる。
- デメリット:
- RESTfulの「リソース指向」から逸脱し、RPC (Remote Procedure Call) に近い設計になる。
- 「いいね」や「いいね取り消し」という操作がリソースの作成や削除にマッピングされないため、HTTPメソッドのセマンティクス(特にPOSTが状態変更を伴う操作全般に使われるため、冪等性の保証などが難しくなる場合がある)を十分に活用できない可能性がある。
- 「like」「unlike」のような名詞ではない動詞をURLに含めることは、RESTfulのベストプラクティスからは推奨されません。
集計データと状態の提供
「いいね数」や「ユーザーがいいね済みか」といった情報は、多くのクライアントで必要とされます。これらの情報をどのようにAPIで提供するかは、設計パターンとは別に検討する必要があります。
一般的な方法としては、メインとなるリソース(例: 記事リソース)の取得APIのレスポンスに含める方法があります。
-
記事詳細取得API (
GET /articles/{article_id}
) のレスポンス例:json { "id": "article-abc-123", "title": "記事タイトル", "content": "記事本文...", "author": { ... }, "created_at": "2023-10-27T09:00:00Z", "updated_at": "2023-10-27T09:30:00Z", "like_count": 150, // いいね数 "is_liked_by_me": true // 認証ユーザーがいいね済みか }
この方法のメリットは、クライアントが記事情報を取得する際に、追加のAPIコールなしにいいね数やユーザーのいいね状態も確認できる点です。特に一覧表示画面などでは、各アイテムのいいね数や自分のいいね状態がまとめて取得できると効率的です。
デメリットとしては、これらの情報を取得するために記事リソースを取得する必要がある点です。また、is_liked_by_me
の情報は認証ユーザーに依存するため、キャッシュ戦略に注意が必要です。
別の方法として、いいね数やいいね状態だけを取得するための専用のエンドポイントを用意することも考えられます(例: GET /articles/{article_id}/like-summary
)。ただし、これはAPIエンドポイントの数を増やすため、上記の「メインリソースに含める」方法の方が多くのケースで効率的です。
考慮事項
- 冪等性: 同じ操作を複数回行っても結果が変わらないように設計することが重要です。例えば、
POST /articles/{article_id}/likes
で既「いいね」済みの場合は、重複して「いいね」を作成せず、既存の「いいね」リソースの情報を返す(HTTPステータスコード 200 OK または 204 No Content とする場合も)ようにすることで冪等性を保てます。 - 認証と認可: 「いいね」や「お気に入り」は通常、認証されたユーザーのみが行える操作です。また、他のユーザーの「いいね」情報にアクセスできる範囲なども認可によって制御する必要があります。
- パフォーマンス: いいね数が非常に多くなる可能性がある場合、いいね数の集計はリアルタイムで行うのではなく、キャッシュを利用したり、非同期に更新したりするなど、パフォーマンスへの配慮が必要です。
- 「いいね」と「お気に入り」の違い: 機能によっては、「いいね」は単発的な評価、一方「お気に入り」は後で参照するためのブックマーク、といった使い分けがある場合があります。それぞれの機能の要件に合わせて、適切なリソース設計を選択してください。独立したリソースとするか、同じリソースでもタイプで区別するかなどが考えられます。
まとめ
RESTful APIで「いいね」や「お気に入り」機能を設計する際には、これらのインタラクションをどのようにリソースとして捉えるかが最初の重要なステップです。
- 「いいね/お気に入り」という事象を独立したリソース (
/likes
) として扱うパターンは、リソース指向に忠実ですが、関連する操作が分散する可能性があります。 - 対象リソースのサブコレクション (
/articles/{article_id}/likes
) として扱うパターンは、操作の対象が明確で直感的ですが、ユーザー別のリスト取得には工夫が必要です。 - 対象リソースへのアクション (
/articles/{article_id}/like
) として扱うパターンは、シンプルですがRESTful原則から逸脱し、RPCライクになりがちです。
これらのパターンの中から、APIを利用するクライアントの種類(Web, モバイルなど)や、機能要件(いいね数や状態の表示頻度、ユーザー別リストの重要性など)を考慮し、トレードオフを理解した上で最適な設計を選択することが求められます。
また、いいね数やユーザーのいいね状態は、メインのリソース取得APIのレスポンスに含めることで、クライアント側の実装をシンプルにし、API呼び出し回数を削減できる場合が多いです。
API設計は、一度決定すると変更が難しいため、将来的な拡張性や保守性も考慮に入れながら、慎重に進めることが大切です。この記事で紹介した考え方やパターンが、皆様のAPI設計の一助となれば幸いです。