Graphql-Mutation_Error_Handling

1. カスタムエラーを使う方法

ドキュメント

具体例

Mutation

graphql-rubyでは GraphQL::Schema::Context#add_error を使うことで複数エラーをレスポンスに含めることができます。

module Mutations
  class CreateUser < GraphQL::Schema::RelayClassicMutation
    argument :name, String, required: true
    argument :age, String, required: true

    field :user, Types::UserType, null: true

    def resolve(name:, age:)
      user = User.new({ name: name, age: age })
      if user.save
        { user: user }
      else
        build_errors(user)
      end
    end

    def build_errors(user)
      user.errors.map do |attr, message|
        message = user[attr] + ' ' + message
        context.add_error(
          GraphQL::ExecutionError.new(
            message,
            extensions: {
              code: 'USER_INPUT_ERROR',
              attribute: attr
            }
          )
        )
      end
    end
  end
end

Response

{
  "data": {
    "user": nil
  }
  "errors": [
      {
        "message": "hoge はすでに存在します",
        "extensions": {
          "code": "USER_INPUT_ERROR",
          "attribute": "name"
        }
      },
      {
        "message": "fuge は一覧にありません",
        "extensions": {
          "code": "USER_INPUT_ERROR",
          "attribute": "age"
        }
      }
    ]
}

メリット / デメリット

メリット

  • スキーマ定義が不要
  • エラーがあれば必ず errors 内に含まれるので、エラーハンドリングがしやすい
  • 少ない実装量ですむ

デメリット

  • スキーマ定義できないため、どんなエラーが渡ってくるか知ることができない
    • システム間で暗黙的にエラーの型の共通認識が必要
    • 密結合になりやすい
    • 通常のフロントエンド <-> バックエンド の構成であれば エラータイプとメッセージだけで事足りるので不便は少ない

採用企業

  • ZOZO
  • 食べログ

2. data内に独自のerrorTypeフィールドを含める方法

ドキュメント

具体例

Mutation

module Mutations
  class CreateUser < GraphQL::Schema::RelayClassicMutation
    argument :name, String, required: true
    argument :age, String, required: true

    field :user, Types::UserType, null: true
    field :error_type, Types::Error::UserErrorType, null: true

    def resolve(name:, age:)
      user = User.create!({ name: name, age: age })
      {
        user: user,
        error_type: nil
      }
    rescue ActiveRecord::RecordInvalid
      build_error(attribute: "name", code: "ALREADY_REGISTERED_NAME")
    end

    private

      def build_error(attribute:, code:)
        {
          user: nil,
          error_type: {
            attribute: attribute
            code: code
          }
        }
      end
  end
end

Response

{
  "data": {
    "user": nil,
    "errorType": {
      attribute: "name",
      code: "ALREADY_REGISTERED_NAME"
    }
  }
  "errors": []
}

メリット / デメリット

メリット

  • スキーマが一致していない時点でエラーになり、気づくことができる
  • どんなエラータイプが渡ってくるか予め分かる(スキーマで知ることができる)
  • 疎結合

デメリット

  • スキーマの更新が必要
  • data の中に明示的にエラーのフィールドを書かなければエラーの情報を呼び出せない
  • 実装量が多くなる

採用企業

  • サーバーサイド同士でGraphQLを使う事例が無いのか、今のところ見当たらず、、、

参照