この記事は kubell Advent Calendar 2025 の記事です。
はじめまして、kubell でバックエンドエンジニアをしている中川です。
今年の4月に新卒で入社し、6月から現在のチームに配属されました。
普段は、既存 API の一部をGraphQL を使用した新しい API に置き換える業務を担当しています。
はじめに
私たちのチームでは、GraphQL を使用した API に「エラーシミュレーション」という機能を実装しています。 最近、この機能に新しいエラーを追加する機会があったので、その仕組みや利点について紹介したいと思います。
エラーシミュレーションとは
エラーシミュレーションとは、本来は特定の条件下でしか発生しないエラーについて、そのエラーレスポンスを意図的に再現できる機能です。
Webやモバイルアプリなど、クライアント側で開発をしていると、「あのエラーの挙動を確認したいけど、なかなか再現できない...」という場面があるかと思います。 例えば、以下のようなものが挙げられます。
- リクエストのレートリミット超過
- メンテナンスモード
- 外部サービス連携のタイムアウト
- サーバーの内部エラー
これらのエラーは、通常の開発中に意図的に発生させることが難しく、エラーハンドリングの実装やデバッグに手間がかかります。 エラーシミュレーション機能を使えば、GraphQL のクエリを叩くだけでこれらのエラーレスポンスを再現できるため、クライアント側のエラーハンドリングを効率的に確認することができます。
使い方
実際の使い方はとてもシンプルです。 以下のような GraphQL クエリを実行するだけで、指定したエラーエスポンスを返させることができます。
クエリ例
query SimulatedError { simulatedErrors { unauthorized } }
レスポンス例
{ "errors": [ { "message": "this is simulated error", "path": [ "simulatedErrors", "unauthorized" ] } ], "data": { "simulatedErrors": { "unauthorized": null } } }
GraphQL のレスポンスは基本的に data と errors の2つのフィールドを持ちます。 正常時は data にデータが入り、エラー時は errors 配列にエラー情報が格納されます。 また、エラーシミュレーションは、通常のクエリと組み合わせて送ることもできます。 これにより、実際のデータ取得フローの中でエラーが発生した場合の挙動を確認することができます。
クエリ例
query { simulatedErrors { unauthorized } viewer { id name } }
なぜ GraphQL でエラーシミュレーションが効果的なのか
エラーシミュレーション自体は REST API でも実現可能ですが、GraphQL と組み合わせることで特に効果を発揮します。 その理由は、GraphQL の Partial Response(部分的レスポンス) という特徴にあります。
Partial Response とは
REST API では、エラーが発生すると通常はリクエスト全体が失敗します。 一方、GraphQL では複数のフィールドを1つのクエリで取得でき、一部のフィールドでエラーが発生しても、他のフィールドは正常にデータを返すことができます。
クエリ例
query { simulatedErrors { externalServiceError } viewer { id name } }
レスポンス例
{ "errors": [ { "message": "this is simulated error", "path": ["simulatedErrors", "externalServiceError"] } ], "data": { "simulatedErrors": { "externalServiceError": null }, "viewer": { "id": "user-123", "name": "田中太郎" } } }
この例では simulatedErrors.externalServiceError でエラーが発生していますが、viewer のデータは正常に取得できています。
クライアント開発での活用シーン
この特徴を活かすと、クライアント側では以下のような開発・デバッグが可能になります。
1. 部分的なエラー表示の確認
画面の一部だけがエラー状態になる UI を簡単にテストできます。 例えば、「プロフィール情報は表示されているが、最近のアクティビティ取得がレートリミットで失敗している」といった状態を再現できます。
クエリ例
query DashboardData { simulatedErrors { tooManyRequests # レートリミット超過をシミュレート } viewer { id name } recentActivities { # このフィールドが失敗した場合の表示を確認したい id content } }
2. エラー時のフォールバック動作の確認
「エラー時にキャッシュされたデータを表示する」「エラー時にリトライボタンを表示する」といったフォールバック処理が正しく動作するかを確認できます。
3. エラーの組み合わせテスト
複数のエラーが同時に発生した場合の UI 表示を確認できます。 例えば、「認証エラーと同時にレートリミットエラーが発生した場合、どちらのエラーメッセージを優先して表示するか」といったケースです。
実装
エラーシミュレーションの機能の実装は、大きく分けて以下の2つのステップで構成されています。
- GraphQL スキーマの定義
- リゾルバーの実装
1. GraphQL スキーマの定義
まず、エラーシミュレーション用のスキーマを定義します。
クエリ例
type Query { simulatedErrors: SimulatedErrors } type SimulatedErrors { unauthorized: Boolean badRequest: Boolean tooManyRequests: Boolean serviceUnavailable: Boolean unexpectedError: Boolean # ... その他のエラー }
SimulatedErrors 型には、シミュレートしたいエラーの種類をフィールドとして定義します。
ここでは、各フィールドの戻り値は Boolean としていますが、エラーシミュレーションが目的なので、フィールド値は null を返しています。
レスポンス例
{ "errors": [ { "message": "this is simulated error", "path": [ "simulatedErrors", "unauthorized" ], "extensions": { "code": "UNAUTHORIZED", "stackTrace": [ "simulated", "(1) attached stack trace" ], "timestamp": 1234567890, "traceId": "1234567890" } } ], "data": { "simulatedErrors": { "unauthorized": null } } }
GraphQL の仕様では、エラーが発生した場合は errors 配列にエラーオブジェクトを格納することが定められています。
エラーオブジェクトの構造は実装者に委ねられており、私たちは extensions フィールドを使ってエラーコードやトレース情報を含める形式を採用しています。
2. リゾルバーの実装
次に、各エラーフィールドのリゾルバーを実装します。
func (r *simulatedErrorsResolver) Unauthorized(ctx context.Context, obj *model.SimulatedErrors) (*bool, error) { return nil, errs.NewUnauthorizedError(errors.New("simulated")) } func (r *simulatedErrorsResolver) TooManyRequests(ctx context.Context, obj *model.SimulatedErrors) (*bool, error) { return nil, errs.NewTooManyRequestsError(errors.New("simulated")) } // ... その他のエラー
リゾルバーは、データとして null (Go のコード上では nil) を返し、第2戻り値としてエラーを返します。これにより、GraphQL のレスポンスでは該当フィールドの data が null になり、errors 配列にエラー情報が格納されます。
応用編 - エラーに優先順位をつける
実際の API では複数のエラー条件が同時に成立することがあります。 その際にどのエラーを返すかはサービスの仕様や構成によって決まってくるため、エラーシミュレーションでも優先順位をつけておくことで実際の API 仕様に合わせたエラーを返すことができます。
優先順位の定義と処理
例えば、ServiceUnavailable > TooManyRequests > Unauthorized のように優先順位を定義し、レスポンスを返す際に、発生したエラーの中から優先順位の高いものを選んで返すようにしておきます。
var errorPriority = []ErrorKind{ ErrServiceUnavailable, ErrTooManyRequests, ErrUnauthorized, // ... その他のエラー種別 } // 優先順位の高いエラーを返す for _, kind := range errorPriority { if err, found := errs[kind]; found { return err } }
動作例
複数のエラーを同時に指定した場合に、優先順位の高いエラーが返されます。
クエリ例
query { simulatedErrors { unauthorized serviceUnavailable } }
上記のクエリでは、serviceUnavailable が優先され、unauthorized は返されません。
これにより、クライアント側のエラーハンドリングの優先順位が、API の挙動と一致しているかを確認できます。
まとめ
この記事では、GraphQL を使用した API におけるエラーシミュレーション機能の実装について紹介しました。
エラーシミュレーションの一番のメリットは、エラーの発生条件を気にせずにデバッグできる点です。 「レートリミットを発生させるために大量リクエストを送る」「エラーを発生させるために複雑な手順を踏む」といった手間が不要になり、クエリ一つで目的のエラーを再現できます。
また、GraphQL の Partial Response と組み合わせることで、「画面の一部だけがエラー状態」といった複雑なケースも簡単に再現でき、より実践的なエラーハンドリングのテストが可能になります。
エラーシミュレーションは、エンドユーザーに直接価値を届ける機能ではありませんが、クライアント開発者のデバッグを楽にすることで、結果的にプロダクトの品質向上にもつながるのではないかと思っています。 普段はユーザーのことを考えて開発をしていますが、社内で私たちの API を使う開発者のことを考えて開発をすることも、同じくらい大切で意味のあることだと今回の実装を通して感じました。
kubell ではエンジニアを募集しています!
少しでも興味を持っていただけた方は、ぜひ覗いてみてください!
www.kubell.com