Skip to content

存在しないユーザーIDでの記事作成時のエラーハンドリング不足 (POST /articles) #17

Description

@claude

🔴 エラー概要

POST /articles エンドポイントで存在しないユーザーIDを指定して記事を作成しようとした際、500 Internal Server Error が返されています。本来は400 Bad Requestとして適切なバリデーションエラーメッセージを返すべきです。

🔍 根本原因

  • 問題箇所: src/lambda/sample-app/index.ts:265

  • 原因: createArticle 関数内で外部User APIのエラーコード E1001(ユーザーが存在しない)を受け取った際、CriticalUserError を throw して処理を中断しています。しかし、外部API仕様書によれば、E1001は「ユーザーが存在しない」というバリデーションエラーであり、500エラーではなく400エラーとして処理すべきです。

  • コードの問題:

// src/lambda/sample-app/index.ts:260-274
// エラーハンドリング
if (!responseData.success) {
    const errorCode = responseData.code;
    const criticalErrors = ['E1001', 'E1003'];

    if (criticalErrors.includes(errorCode)) {
        throw new CriticalUserError(
            'EXTERNAL_SERVICE_ERROR',
            'External service error',
            {
                externalErrorCode: errorCode,
                authorId,
                operation: 'createArticle'
            }
        );
    }
    // ...
}

問題点:

  1. E1001はバリデーションエラー: 外部API仕様書(docs/api-specs/user-api.yaml:51-80)によれば、E1001は「ユーザーが存在しない」エラーであり、データ整合性の問題です。記事作成時に存在しないユーザーIDが指定された場合は、ART-003(バリデーションエラー)として400エラーを返すべきです。

  2. E1003も同様にバリデーションエラー: E1003は「ユーザーアカウント削除済み」であり、削除済みユーザーで記事を作成することは許可されるべきではありません。これもバリデーションエラーとして処理すべきです。

  3. エラーメッセージが曖昧: "External service error" という曖昧なメッセージでは、クライアント側で何が問題なのか分かりません。「指定されたユーザーIDが存在しません」のような具体的なメッセージが必要です。

📊 影響範囲

  • 影響を受ける機能:
    • POST /articles (記事作成)
  • 影響を受けるユーザー:
    • API クライアント開発者(存在しないユーザーIDで記事を作成しようとした場合、500エラーが返され、何が問題なのか分からない)
    • フロントエンド開発者(バグやデータ不整合により無効なユーザーIDが送信された場合)
  • 深刻度: High
    • 500エラーはサーバー内部エラーを意味するため、クライアント側で適切なエラーハンドリングができない
    • 本番環境でアラートが発生し、不要な調査工数が発生する
    • ユーザー体験の悪化(適切なエラーメッセージが表示されない)

🛠️ 修正方法

createArticle 関数のエラーハンドリングを修正し、E1001/E1003を Critical エラーではなくバリデーションエラーとして処理します。

// src/lambda/sample-app/index.ts:260-281
// エラーハンドリング
if (!responseData.success) {
    const errorCode = responseData.code;

    // E1001: ユーザーが存在しない → バリデーションエラー
    if (errorCode === 'E1001') {
        return createErrorResponse(400, 'ART-003', '記事の作成に失敗しました', {
            invalidFields: {
                authorId: '指定されたユーザーIDが存在しません',
            },
            externalErrorCode: errorCode,
        });
    }

    // E1003: ユーザーアカウント削除済み → バリデーションエラー
    if (errorCode === 'E1003') {
        return createErrorResponse(400, 'ART-003', '記事の作成に失敗しました', {
            invalidFields: {
                authorId: '指定されたユーザーは削除済みです',
            },
            externalErrorCode: errorCode,
        });
    }

    // E1002 (アカウント停止), E2001 (レート制限), E3001 (内部エラー) などはフォールバック処理
    console.warn(`User API returned error during article creation: ${errorCode}`, {
        authorId,
        errorCode
    });

    // その他のエラーは警告のみで処理を継続(既存のロジック)
}

重要な変更点:

  • E1001とE1003を CriticalUserError として throw するのではなく、400 Bad Request として適切なエラーレスポンスを返す
  • エラーメッセージを具体的に(「ユーザーIDが存在しません」「削除済みユーザーです」)
  • クライアント側で適切にエラーをハンドリングできるようにする

🚀 再発防止策

  1. 外部API仕様書の理解を深める: 外部APIのエラーコードの意味を正しく理解し、適切なエラーハンドリングを行う。「Critical」という分類は、本システムにとって致命的かどうかではなく、外部API側の分類であることに注意。

  2. エラーコード分類の見直し:

    • E1001(ユーザー不存在)とE1003(削除済み)はバリデーションエラー → 400エラー
    • E1002(アカウント停止)はフォールバック可能 → 警告のみ、デフォルト値で処理継続
    • E2001(レート制限)はリトライ可能 → 警告のみ、またはリトライロジック
    • E3001(内部エラー)は真の Critical エラー → 500エラー or リトライ
  3. 統合テストの追加:

    • 存在しないユーザーIDで記事作成を試みるテストケース
    • 削除済みユーザーIDで記事作成を試みるテストケース
    • それぞれが400エラーを返すことを確認
  4. 同様の問題の確認: getArticleById 関数でも同じパターンでE1001/E1003を Critical として扱っているため、そちらも修正が必要。ただし、getArticleById の場合は記事取得のコンテキストなので、フォールバック処理(デフォルト値での継続)が適切な可能性もある。

📝 検知情報

エラータイプ: CriticalUserError
エラーメッセージ: External service error
発生時刻: 2025-11-27T09:39:09.203Z
コンテキスト: POST /articles
ログソース: /aws/lambda/llm-ops-sample-app
スタックトレース
CriticalUserError: External service error
    at createArticle (/var/task/index.js:212:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async Runtime.handler (/var/task/index.js:309:20)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions