APIのエラーハンドリング

最近チーム内でエラーハンドリングに関する話が繰り返し出たので今日はエラーハンドリングについて少しまとめてみたいと思います。

例外の種類

プログラムの実行中に何らかの例外が発生することは実装時に想定されていることと思います。

ただ、全ての例外を十把一絡げに扱って良いかと言うとそうではないはずです。

大きく分けると以下の2つに分けられると思います。

  • 入力値が間違っていたり認証エラー等
  • 上記以外の実行時エラー

前者はHTTPレスポンスステータスコードで言うところの400系に当たります。

ユーザーが誤った値を入力することは十分に想定できることです。

なのでこれらをいちいちSentry等にレポートしていると他の重要なレポートが埋もれてしまいます。

一方で後者はステータスコードで言うと500 Internal Server Error等が該当します。

例えばDBへの接続に障害が発生したりして処理ができない場合等です。

こういった例外はサービスの稼働状況に直結する可能性がある為、適切にレポーティングする必要があります。

Laravelのエラーハンドリング

すべての例外は、App\Exceptions\Handlerクラスが処理します。このクラスは、カスタム例外レポートとレンダリングコールバックを登録できるregisterメソッドを持っています。こうした各概念について詳しく説明します。例外レポートは、例外をログに記録したり、Flare、Bugsnag、Sentryなどの外部サービスへ送信したりするために使用します。デフォルトで例外はログ設定に基づいてログに記録します。ただし、必要に応じて例外を自由に記録できます。

エラー処理 8.x Laravel

↑にある通り、Laravelでは全ての例外をHandlerクラスが処理します。

Handlerクラスの役割はざっくり言うとログへの記録と何を返すかを設定することです。

しかし、先程述べたように全ての例外をSentry等へレポートする必要はありません。

なので400系の例外はそもそもHandlerに渡さずに適切な形でreturnする、という形を取るのが良いと思います。

レポートすべき例外とそうでない例外を切り分けて適切に処理することが必要かと思います。

エラーレスポンス

例外の種類によって処理を切り分ける、という話をここまでしましたが、最終的に返すレスポンスは統一された規格を用いるのが良いと思います。

エラーレスポンスが統一されていると、フロントエンドでの処理も綺麗に整理することができ、無駄な分岐をせずに済みます。

エラーレスポンスの規格としてはRFC 7807で定められています。

こちら見てみると冒頭に、

HTTP [RFC7230] status codes are sometimes not sufficient to convey enough information about an error to be helpful. While humans behind Web browsers can be informed about the nature of the problem with an HTML [W3C.REC-html5-20141028] response body, non-human consumers of so-called "HTTP APIs" are usually not.

This specification defines simple JSON [RFC7159] and XML [W3C.REC-xml-20081126] document formats to suit this purpose. They are designed to be reused by HTTP APIs, which can identify distinct "problem types" specific to their needs.

とあるので要はステータスコードだけだと不十分だよね、問題の詳細も機械が読める形式で返すように設計したよってことのようです。

現在チーム内のプロダクトのエラーレスポンスもこれに統一しようとしているところです。

PHPだとCrell/ApiProblemphpro/api-problem等のパッケージを使えば対応できそうです。

まとめ

以上エラーハンドリングについてでした。

基本的にはRFC等の規約に沿った形で実装するのが良いと思います。

最終的に返すレスポンスはRFCに合わせるとして、ではどういう例外があってそれぞれどう処理するのが適切かを考える、という感じで逆算しながらコードに落とし込むという感じでしょうか。

引き続き勉強しつつ、実践していきたいと思います。