RESTful API データモデリング

RESTful APIで非同期処理を扱うデータモデリング:状態管理と通知パターン

Tags: API設計, データモデリング, RESTful API, 非同期処理, ポーリング, Webhook

はじめに

多くのウェブアプリケーションやサービスでは、リクエストを受け取ってから完了までに時間がかかる処理が存在します。例えば、大量データのインポート、複雑なレポート生成、外部システムとの連携などです。これらの処理を同期的に行ってしまうと、クライアントは処理が完了するまで待機する必要があり、ユーザー体験の低下やタイムアウトの問題を引き起こす可能性があります。

このような場合、処理を非同期で行い、APIとしてはリクエストを受け付けたことを即座にクライアントに返す設計が採用されます。RESTful APIで非同期処理を扱う際には、処理の進行状況をどのように表現し、結果をどのようにクライアントに伝えるかが重要な設計課題となります。ここでは、非同期処理を組み込んだRESTful APIのデータモデリングについて、具体的なアプローチと考え方を解説します。

RESTful APIと非同期処理の課題

RESTful APIは通常、クライアントからのリクエストに対して、サーバーが即座に処理を行い、その結果をレスポンスとして返す「同期的な」モデルに基づいています。しかし、非同期処理では、リクエストを受け付けた時点では最終的な結果が出ていません。このため、以下のような課題が生じます。

これらの課題を解決するためには、非同期処理自体を一つの「リソース」として捉え、その状態や結果をデータモデルとして適切に設計する必要があります。

非同期処理のデータモデリングの基本

非同期処理をRESTful APIに組み込む際の基本的な考え方は、実行された非同期処理を一つのリソースとして表現することです。このリソースは、一般的に「ジョブ」や「タスク」などと呼ばれます。

ジョブリソースの定義

非同期処理を表すジョブリソースは、その処理を一意に識別するための情報、現在の状態、そして最終的な結果やエラーへの参照を持つべきです。基本的な属性としては以下が考えられます。

非同期処理開始時のフローとレスポンス

クライアントが非同期処理をサーバーに依頼する場合、通常は以下のような流れになります。

  1. クライアントが特定のエンドポイントにリクエスト(例: POST /reports でレポート生成を依頼)を送信します。
  2. サーバーは非同期処理を開始(キューに入れるなど)し、即座にレスポンスを返します。
  3. このレスポンスには、開始された非同期処理を表すジョブリソースの情報を含めることが一般的です。最低限、そのジョブのIDと現在の状態、そして後から状態や結果を確認するためのURLを含めます。

リクエスト例:

POST /reports/generate
Content-Type: application/json

{
  "reportType": "salesSummary",
  "startDate": "2023-01-01",
  "endDate": "2023-12-31"
}

レスポンス例 (202 Accepted):

{
  "job_id": "report-job-12345",
  "status": "PENDING",
  "status_url": "/jobs/report-job-12345"
}

ここでステータスコードに 202 Accepted を使用するのは適切です。これは、リクエストは受理されたものの、処理は完了していないことを意味します。レスポンスボディには、後続処理に必要な情報(ジョブID、状態確認用URL)を含めます。

非同期処理の結果取得と通知パターン

非同期処理が開始された後、クライアントがその結果を知るための主なパターンは以下の2つです。

  1. ポーリング (Polling): クライアントが定期的にサーバーに問い合わせてジョブの状態を確認する方式。
  2. コールバック/Webhook: サーバー側で処理が完了した際に、事前に指定されたクライアント側のURLに通知を送信する方式。

1. ポーリングパターン

ポーリングパターンでは、クライアントは最初に受け取ったジョブIDまたは状態確認用URLを使用して、定期的にジョブリソースを取得します。

状態確認のリクエスト例:

GET /jobs/report-job-12345

状態確認のレスポンス例 (ジョブ実行中):

{
  "job_id": "report-job-12345",
  "status": "PROCESSING",
  "created_at": "2024-01-20T10:00:00Z",
  "updated_at": "2024-01-20T10:05:00Z",
  "status_url": "/jobs/report-job-12345"
}

状態確認のレスポンス例 (ジョブ完了):

{
  "job_id": "report-job-12345",
  "status": "COMPLETED",
  "created_at": "2024-01-20T10:00:00Z",
  "updated_at": "2024-01-20T10:15:00Z",
  "status_url": "/jobs/report-job-12345",
  "result_url": "/reports/sales-summary-2023" // 結果リソースへのURL
}

ジョブが完了(COMPLETED)ステータスになったら、クライアントは result_url を見て、結果を取得するための別のリクエストを送信します。

結果取得のリクエスト例:

GET /reports/sales-summary-2023

結果取得のレスポンス例:

{
  "period": "2023",
  "totalSales": 123456789,
  "items": [ ... ] // レポートデータ本体
}

ポーリングパターンのメリット・デメリット:

2. コールバック/Webhookパターン

Webhookパターンでは、クライアントは非同期処理を開始する際に、結果通知を受け取りたいURL(コールバックURL)をサーバーに伝えます。サーバーは処理完了後、そのURLに対してHTTPリクエストを送信して結果を通知します。

コールバックURLを指定して非同期処理を開始するリクエスト例:

POST /reports/generate
Content-Type: application/json

{
  "reportType": "salesSummary",
  "startDate": "2023-01-01",
  "endDate": "2023-12-31",
  "callbackUrl": "https://client.example.com/api/webhook/report-status"
}

レスポンス例 (202 Accepted):

{
  "job_id": "report-job-67890",
  "status": "PENDING" // callbackUrl を指定した場合は status_url は不要な場合も
}

サーバーからクライアントへのWebhook通知例:

サーバーは処理完了後、指定された callbackUrl へPOSTリクエストを送信します。ペイロードには、完了したジョブの情報を含めます。

POST https://client.example.com/api/webhook/report-status
Content-Type: application/json

{
  "job_id": "report-job-67890",
  "status": "COMPLETED",
  "result_url": "/reports/sales-summary-2023", // 結果リソースへのURL
  "timestamp": "2024-01-20T10:20:00Z"
}

クライアントは、この通知を受け取ったら、必要に応じて result_url から結果の詳細を取得します。

Webhookパターンのメリット・デメリット:

どちらのパターンを選択するかは、アプリケーションの要件(リアルタイム性、クライアントの能力、システム構成など)によって異なります。両方をサポートする場合もあります。

状態遷移とエラーハンドリングのデータモデリング

状態遷移

ジョブリソースの status は、非同期処理の進行に伴って変化します。一般的な状態遷移は以下のようになります(例)。

ジョブリソースの表現において、status フィールドはEnum型やあらかじめ定義された文字列セットとしてモデル化します。これにより、クライアントはステータスの取りうる値を予測できます。

エラーハンドリング

非同期処理が失敗した場合(status: FAILED)、ジョブリソースにはその原因を示すエラー情報を含めるべきです。これは error フィールドとしてネストされたオブジェクトで表現すると良いでしょう。

error オブジェクトの例:

"error": {
  "code": "REPORT_GENERATION_FAILED", // エラータイプを識別するコード
  "message": "Failed to generate report due to data processing error.", // 人間が読めるメッセージ
  "details": { // 詳細な情報(オプション)
    "processingStep": "data aggregation",
    "internalErrorCode": "ERR1001"
  }
}

クライアントはジョブの状態が FAILED になった際に、この error オブジェクトを解析して、エラーの原因を特定したり、ユーザーに適切なメッセージを表示したりできます。

考慮事項

まとめ

RESTful APIで非同期処理を扱う場合、単に処理をバックグラウンドで実行するだけでなく、その「処理自体」を一つのリソースとして適切にデータモデリングすることが重要です。ジョブリソースを導入し、その状態、結果、エラーを明確に表現することで、クライアントは処理の進行状況を追跡し、結果を安全かつ効率的に取得できるようになります。

ポーリングとWebhookは結果通知の主要なパターンであり、それぞれにメリットとデメリットがあります。アプリケーションの特性に応じて最適なパターンを選択するか、両方を組み合わせて提供することを検討してください。

非同期処理のデータモデリングは、APIの使いやすさ、信頼性、そしてスケーラビリティに大きく影響します。ここで解説した基本的な考え方やパターンを参考に、ご自身のAPI設計に役立てていただければ幸いです。