Affamative Way

前向きにグダグダいいながらコード書く

REST API Common Spec としての HTTP Status Code と Error の提案

この記事は Timee Advent Calendar 2023 シリーズ 2 の14日目の記事です。

qiita.com

はじめに

CTO室に10月に入社した id:cos31 です。 色々とやり始めたばかりで、ネタに苦しいので仕事にもタイミーには全く関係のないネタになります!*1

TL;DR

API HTTP Response Code Definition

RFC 9205 - Building Protocols with HTTP (日本語訳) の方針を参考に HTTP Status Codeへのマッピングを無理に行いません。

※ 当該セクションの要約

  • HTTP Status Code は、多くの場合、アプリケーション自体以外でも生成されます、例えばネットワークエラーや、Proxy、CDN、LB、サーバ自体が過負荷になっている場合などです。特定のエラーを受けた際に、クライアントは汎用的なセマンティクスである場合に誤解します。
  • 個々のエラーに HTTP Status Code をアプリケーションエラーを 1対1 マッピングすると既存の HTTP Status Code を利用するなど、多くの悪いプラクティスに繋がります。
  • HTTPを使用するアプリケーションは、エラーを定義して最も適用可能な HTTP Status Code を使用し、疑わしい場合は一般的な HTTP Status Code (200、400、および500)を寛大に使用する必要があります
  • 同じ HTTP Status Codeマッピングされた複数のエラー条件を区別し、上記の誤った分布の問題を回避するために、HTTPを使用したアプリケーションは、応答のメッセージコンテンツおよび/またはヘッダーフィールドにより細かいエラー情報を伝える必要があります。

上記を踏まえて、 HTTP Status CodeAPIの成功または失敗を応答します。

  • 2xx 範囲の Status CodeRequest が正常に処理されたことを示します。
  • 4xx 範囲の Status CodeAPIクライアントが指定した Request 要求が正しくないことを示します。
  • 5xx 範囲の Status CodeAPIサーバーでのエラーを示します。
  • 3xx 範囲の Status Code は 利用しません

2xx

Request がサーバーで正常に処理されたことを示します。

長さがゼロの payload ボディや空の payload を生成してもかまいませんが、200レスポンスは常に payload を持ちます。サーバーが応答に payload を送信したくない場合は、代わりに HTTP ステータス 204(No Content)を利用します。

ステータス 説明
200 OK Request が受け入れられました。応答には結果が含まれています。 GET では、Request されたリソースまたはデータは応答本文内にあります。 PUT または DELETE では、Request は正常に行われ、 payload には結果に関する情報 (新規リソース ID やリソース情報、変更後の情報など) があります。
201 Created この応答コードは PUT または POST の場合に利用します、新規リソースが正常に作成されたことを示しています。 payload には、例えば新規リソースに関する情報や検証情報 (アセットが更新された場合など) が含まれていることがあります。
202 Accepted Request は受け入れられたが、処理が完了していないことを示します。この HTTP Status Code は、実際の操作が本質的に非同期である場合に役立ちます。
204 No Content Request は受け入れられたが、何も戻されなかったことを示します。 これは、Request が処理されても、結果に関する追加情報が戻されなかった場合に戻されます。

Example

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1234,
  "name": "hoge",
  "category": "fuga",
}

4xx

クライアントの Request 要求での不正時に利用します。これは、パラメータの指定や、パラメータに指定されたリソースに対する状態の不備時(指定されたリソースが存在しない、指定されたステータスへの変更ができない場合など)が含まれます

ステータス 説明
400 Bad Request Request が無効な場合に利用します。 このコードは、サーバーが処理しようとしたが、要求の一部が無効であった (例えば、不正なRequest 構文、無効なRequest メッセージパラメータ、指定のリソース自体が存在しない) 場合に戻されます。 Request のエラーに関する情報は、応答本文に記載されており、type と title を含んでいます。
401 Unauthorized 認証に失敗した場合に、サーバが応答します。
403 Forbidden アクセス権限のないリソースにアクセスしようとしたことを示します。具体的には、access token に対しての scope が不足している場合などに用いられます
404 Not Found 指定された path に対する処理が存在しないことを示します。指定されたリソースが存在しない場合には 400 を利用してください
405 Method Not Allowed Request された HTTP メソッドをサポートしていない場合に応答します。
406 Not Acceptable Accept Request ヘッダ に application/json 以外が要求さた場合に応答します。
415 Unsupported Media Type Content-Type が Request ヘッダで示されるクライアントが提供したメディアタイプを、APIが処理できないことを示します。

5xx

サーバ側での問題を示します。

ステータス 説明
500 Internal Server Error サーバーで内部エラーが発生した場合に応答します。 エラー情報は応答本文にあります。
503 Service Unavailable サーバーがメンテナンス中のために停止していることや、過負荷状態になっている場合に応答します。依存する外部サービスや、インフラ基盤側での問題時に利用されます

Errors

Problem Details

4xx 範囲のプログラムで処理可能なエラーについては、RFC 9457 Problem Details for HTTP APIs (日本語訳) に従って詳細なエラー情報を含みます。

エラー応答時には Content-Type: application/problem+json としてエラー詳細を payload に記載し返却します

Problem Detail Object (bia: RFC 9457 #Section 3.1 区分 より以下のMemberを採用して利用します。 *2

member type condition detail
type string required 問題のタイプを識別するURI参照。
title string required 人間が読める形式の問題タイプの概要。ローカライゼーションの目的を除いて、問題の発生ごとに変更するべきではありません
detail string この問題の発生に固有の、人間が読める説明

また、Memberを拡張として、追加する場合には type毎に変動がないようにAPI毎に定義します。

type Format

https://example.com{/path}

pathは 半角小文字英数字 + _ ( [0-9a-z\_]+ ) とします。

# Good
https://example.com/validation_error

# Bad
https://example.com/validation-error

Common types

type path title usage additonal member
/request_unauthorized API key authentication failed 認証失敗した場合 -
/json_invalid Your request json invalid JSON形式が不正の場合 -
/validation_error Your request parameters didn't validate OpenAPIなどの data schema としての validation error の場合や、DB データとの整合性を検証した validation error の場合 invalid_params: [] ※失敗したパラメータ箇所を列挙
/resource_not_found Specified resource does not exist 指定したResourceが存在しない場合(※ コンテキストに応じて論理削除である状態も含む) detail: 存在しなかった対象を明示
/resource_already_exist Specified resource already exist 重複して指定したResourceを作成しようとした場合 detail: 失敗対象の明示
/mutation_error Mutation failed DBへの変更操作(e.g. create, update, delete)に失敗した場合 detail: 失敗対象の明示
/server_error Something wrong occurs サーバー側での不測エラーが発生した場合(※ 500 Internal Server Error への対応を想定) -
/user_unavailable Specified user unavailable 指定したユーザーが処理を継続できない状態(e.g. ステータスが無効な状態) の場合 detail: 何故できないかの詳細を記載します

Example

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
    "type": "https://example.com/unauthorized",
    "title": "API Key Authenticate failed"
}

detail が入るパターン

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
    "type": "https://example.com/resource_not_found",
    "title": "Specified resource does not exist",
    "detail": "User ID: 1234 not exists"
}

• Memberを拡張するパターンのSample

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "https://example.net/validation_error",
  "title": "Your request parameters didn't validate.",
  "invalid_params": [
    {
       "name": "age",
       "reason": "must be a positive integer"
    },
    {
       "name": "color",
       "reason": "must be 'green', 'red' or 'blue'"
    }
  ]
}

まとめ

これくらい迷わない定義があると共通理解として整理もできて、クライアントとしても監視観点としても整理が明確になると思っています

しかしながら、めんどくさい部分としては、Problem Detailtype に 応じた Collection の増減の型定義ですが、microservice などでドメインが揃うAPI郡などでまとまると見通しが良い範囲に収まると思います

実例としては、Stripe では、type 指定された URI に対してドキュメントが準備されており、大変クライアントフレンドリーとなっています stripe.com

他、APIとしての共通的な振る舞いに悩んだ場合には、 https://google.aip.dev/ を見ましょう。とても良いです。

特に Pagination とか独自提示したくもないけど標準仕様もない悩ましい Collection Resource への定義は秀逸なので是非見てください

google.aip.dev

このように自身で考えるよりも、標準仕様や先人たちの考えて成果にうまく乗っかりながら、程よいアレンジをして共通仕様として育てると治安の良いAPIになると思います!

We’re Hiring!

タイミーでは、ともに働くメンバーを募集しています!!

現在募集中のポジションはこちらです! hrmos.co

「より深く話を聞きたい」とか「で、お前今何やってんの?」と思われた方は、是非一度カジュアル面談でお話ししましょう!

product-recruit.timee.co.jp

*1:前職でもアーキテクトやってきたんですが、しばらくはやらなそうなので忘れるためのまとめ

*2:status や instance も改定時に追加されましたが、ユースケースの場合分けが難しいのと必要時に個別に検討することを期待しての、まずは最低限として定義