記事作成の背景(はじめに)
個人開発をしていた当初、バックエンドAPIのエラーハンドリングについて**「エラーが発生したらとりあえず例外を throw してLambdaをクラッシュさせておけば、デフォルトの Errors メトリクスが反応して検知が楽だし、これでいいのでは?」**と安易に考えていました。
結論から言うと、この「なんでも throw する」アプローチは、特にフロントエンドと同期通信を行うAPIにおいてはアンチパターンです。
AWS公式ドキュメントでも以下のように明記されている通り、クライアント側でハンドリングすべきエラー(入力値のバリデーションエラーなど)は例外を throw するのではなく、適切なHTTPステータスコードとメッセージをレスポンスオブジェクトとして返す(return する)必要があります。
Node.jsのLambda関数でレスポンスを返却するコード例(AWS公式より抜粋・要約)
- 正常な結果を返す場合:
{"statusCode": 200, "body": "..."}などのオブジェクトを正常にreturnする。- システム全体の致命的な例外(クラッシュ)とする場合: 例外をそのまま
throwする。- クライアント側のエラー(例:必須パラメータ不足)を返す場合: 例外をスローせず、
{"statusCode": 400, "body": "..."}などのエラー情報を乗せたオブジェクトを正常にreturnする。
本記事では、以下の点についてまとめます。普段アプリ開発などをしている方にとっては、結構当たり前の内容になっているかもしれないです...
- なぜ無闇な
throwがアンチパターンとなるのか- ユーザー体験(UX)の崩壊
- システム内部情報の漏洩(セキュリティリスク)
- アラートの過剰検知
- 逆にどのような時に
throwすれば良いのか- 同期処理における
throw - 非同期処理における
throw
- 同期処理における
なぜ無闇な throw がアンチパターンとなるのか
1. ユーザー体験(UX)の崩壊
API Gatewayを介してフロントエンドと同期通信(リクエスト-レスポンス型)を行うLambda関数において、例外をキャッチせずにそのまま throw してクラッシュさせてしまうと、API Gatewayの仕様によってクライアントには一律で 502 Bad Gatewayや 500 Internal Server Error といった大雑把な汎用エラーが返却されます。
入力値のミス(400)なのか、一時的な混雑(503)なのか、致命的な障害(500)なのかが区別できないため、再試行すべきかどうかの判断すらつかず、ユーザーの離脱を招きます。エラー内容に応じた改修ができないなどデメリットが多いです。
2. システム内部情報の漏洩(セキュリティリスク)
例外をそのまま投げっぱなしにしてクラッシュさせると、言語のランタイムによっては詳細なスタックトレース(ディレクトリ構造や内部ライブラリのパス)がそのままクライアントへのレスポンスに含まれてしまうリスクがあります。これは攻撃者に脆弱性のヒントを与えることに繋がります。
3. アラートの過剰検知
一時的なバリデーションエラー(入力値エラー:400 Bad Request)など、プログラムとしては想定内のエラーまで throw してしまうと、CloudWatchの Errors メトリクスが上昇します。結果として「本当のシステム障害」と「ただのユーザー入力ミス」の区別がつかなくなる、などデメリットが多いです。
逆にどのような時に throw すれば良いのか
非同期処理においては、むしろthrow するのが正解!
Lambdaのエラーハンドリングは、その呼び出しタイプが 「同期(Request-Response)」 か 「非同期(Event / Queue連携)」 かによって、取るべきアプローチが180度変わります。それぞれの特徴とエラー処理設計の違いは以下の通りです。
| 比較項目 | 同期呼び出し | 非同期呼び出し / イベントソース |
|---|---|---|
| 主なユースケース | 即座に応答が必要なWeb APIなど | バックグラウンド処理、外部通知、バッチ処理など |
| 代表的なイベントソース(AWS) | API Gateway, ALB | SQS, SNS, S3 Event, EventBridge |
| 処理の流れ | クライアントはレスポンスが返ってくるまで同期して待機する。 | クライアントはイベント(キュー等)を投入して即離脱し、実行完了を待たない。 |
| エラー時の推奨アクション | キャッチして正常終了(return)させる |
例外をスローして異常終了(throw)させる |
| インフラの自動連動 |
return したオブジェクトを元に、API Gateway等が任意のHTTPステータス(400/500など)とJSONメッセージをフロントへ返却する。 |
throw されたエラーを検知して、AWSインフラ側が自動で再試行(リトライ)を行ったり、DLQ(デッドレターキュー)へ退避する。 |
| 誤った設計をした場合の弊害 |
throw してクラッシュさせると、API Gatewayが 502 や 500 などの一律な大雑把エラーを返し、UXの崩壊やセキュリティリスク(スタックトレース露出)に繋がる。 |
try-catch でキャッチして正常終了(return)させてしまうと、AWS側は「正常に処理された」と見なすため、リトライやDLQが走らずメッセージ(データが消失する。 |
まとめ
-
同期API(Request-Response):
throwでクラッシュさせず、try-catchで安全なエラーメッセージをreturnする。 -
非同期・イベント駆動: 積極的に
throwしてクラッシュさせ、AWSインフラ側のリトライ・DLQに処理を委ねる。
書籍の紹介
AWS Lambdaの設計・開発におけるエラーハンドリングについて書かれた本です。
-
Programming AWS Lambda(John Chapin, Mike Roberts 著 / O'Reilly Media)
- 第3章「Programming AWS Lambda Functions」の「Invocation Types & Logging」、および**第7章「Logging, Metrics, and Tracing」**が特に参考になります。同期(Request-Response)と非同期(Event)における例外処理の設計の違いや、CloudWatchを活用したロギングと監視のベストプラクティスが詳細に解説されています。
