grpcerror

package module
v0.0.0-...-b05257f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 4, 2023 License: MIT Imports: 5 Imported by: 0

README

grpcerror

grpcerror は gRPC駆動アプリケーションのためのエラーハンドリングユーティリティです。

できること:

  • エラー翻訳: 依存gRPCサービスから返却されたエラー翻訳を行うための Translate関数と gRPC Interceptorを提供します
  • エラー生成ヘルパ: status.Newerrdetails の設定のためのヘルパ関数を提供します

使い方

エラー翻訳

gRPCアプリケーション向けに、別のgRPCサービスが生成したエラーの翻訳ユーティリティを提供します。これにより、あなたのアプリケーションでは依存gRPCサービスのエラーを起因とする誤ったエラーハンドリングを防止することができます。

問題意識

あるgRPC APIハンドラが、別のgRPCサービスを呼び出した結果エラーを得たとき、API実行の継続可否を踏まえ、エラーハンドリングを行う必要があります。

  • API実行を継続できるエラーであれば、エラーを対処して実行を継続する。
  • API実行を継続できないエラーであれば、エラーを適切なエラーに翻訳した後に、クライアントに返却する。

このうち、後者であればエラー翻訳を行う必要があります。そのままクライアントにエラーを返却すると、誤ったエラーを伝えることになるからです。 これはクライアント側でエラーハンドリングを妨げるため、バグの原因になります。また、APIのエラーレートを監視する場合において、正しいエラーコードが観測できなくなるために、false positive / false negative の原因となります。

エラー翻訳の方法は状況と文脈に依存してケースバイケースとなります。 APIが返却するべきエラーは、以下のような考え方によって決めることが多いと考えます。

  • それがコンテキストやタイムアウトに起因するエラーであれば、codes.Canceled / codes.DeadlineExceeded に翻訳する
  • 呼び出し側クライアントに起因するエラーであれば codes.InvalidArgument, NotFound, FailedPrecondition などに翻訳する
  • サーバー実装に起因するエラーや、依存gRPCサービスの実装に起因するエラーであれば codes.Internal / codes.Unknown に翻訳する

これを踏まえて、依存gRPCサービスが返却したエラーのハンドリング方法は以下のようになることが多いと考えます。

依存gRPCサービスが生成したエラーコード APIが返却するべきエラーコード 理由
Canceled Canceled クライアントがキャンセルした場合はAPIはキャンセルコードを返すべき(クライアントに責任があるため)
DeadlineExceeded DeadlineExceeded クライアントが設定したタイマーにタイムアウトした場合はタイムアウトコードを返すべき(クライアントに責任があるため)一方、サーバーが設定したタイマー起因である場合はこの限りではない
Unknown Unknown or Internal 依存gRPCサービス実装に起因するエラーであり、サーバーがそのサービスに依存している以上、サーバーの責任である
InvalidArgument Internal サーバーが依存gRPCのリクエスト契約に違反しているため、サーバーの責任である
それ以外 Internal 基本的には同上。

もちろん、状況と文脈次第で、例外ケースも多々あります。例えば、NotFound エラーを例にして、クライアントが外部gRPCサービスが保有しているリソースのIDを指定し、APIサーバーにリクエストを行う場合を考えます。 このような場合、APIサーバーが外部gRPCサービスにリクエストした結果 NotFound エラーとなるのであれば、APIサーバーはクライアントに NotFound エラーを返却するべきでしょう。

これらを踏まえると、特別なケースではエラー翻訳を都度行うべきであるが、デフォルトのエラー翻訳ルールを適用できる場合はそれを利用するべきであると考えます。 このユーティリティは、このポリシをアプリケーションに簡単に実装できるヘルパーを提供しています。

利用方法

デフォルトのエラー翻訳は、gRPC Server Interceptorとして設定します。

server := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        interceptor.TranslateUnaryServerInterceptor(interceptor.DefaultTranslator()),
    ),
)

interceptor.DefaultTranslator() は 上記表に規定されたルールに従って翻訳を行います。必要に応じて、interceptor.MapTranslator を利用することで挙動をカスタマイズできます。

都度の外部gRPCサービス起因のエラー対処は、以下のようになります。

resp, err := mayOccurGRPCError()
if err != nil {
    // デフォルトのエラー翻訳ポリシーを適用したい場合は、何もせず return する
    return nil, err 
}

resp, err := mayOccurGRPCError()
if err != nil {
    // Wrapしても、デフォルトのエラー翻訳ポリシーが採用される
    return nil, fmt.Errorf("xxx.yyy  failed: %w", err)
}

resp, err := mayOccurGRPCError()
if err != nil {
    if status.Code(err) == codes.NotFound {
        // NotFound に明示的にエラー翻訳することで、デフォルトのエラー翻訳ポリシの適用を回避する
        return nil, grpcerror.Translate(err, grpcerror.NotFound("not found")) 
    }
    return nil, err
}

resp, err := checkPermission()
if err != nil {
    // PermissionDenied エラーを自ら生成し、デフォルトのエラー翻訳ポリシの適用を回避する。
    return nil, grpcerror.AsIs(grpcerror.PermissionDenied("permission denied")) 
}

エラー生成ヘルパ

gRPCエラーを生成するためのヘルパ関数を提供します。 これにより、あなたのアプリケーションに存在するgRPCエラーを返却する場合のボイラープレート(or あなたのプロジェクトに存在するヘルパー)を削減することができます。

単純な例
resp, err := mayOccurGRPCError()
if err != nil {
    return nil, status.Error(codes.AlreadyExists, "already exists")
}

ヘルパ関数を用いることで、上記のようなコードを、以下のように置き換えできます。

resp, err := mayOccurGRPCError()
if err != nil {
    return nil, grpcerror.AlreadyExistsError("already exists")
}
エラー詳細の例
resp, err := mayOccurGRPCError()
if err != nil {
    serr := status.New(codes.InvalidArgument, "invalid argument")
    detail, err := serr.WithDetails(&errdetails.BadRequest{
        FieldViolations: []*errdetails.BadRequest_FieldViolation{
            {
                Field:       "name",
                Description: "len(name) must be [1, 32]",
            },
        },
    })
    if err != nil {
        return nil, status.Error(serr.Code(), fmt.Sprintf("%s: with defailt failed: %v", serr.Err(), err))
    }
    return nil, detail.Err()
}

このようなコードを以下のように書き換えることができます。

resp, err := mayOccurGRPCError()
if err != nil {
    return nil, grpcerror.InvalidArgumentError("invalid argument", grpcerror.BadRequest(
        &errdetails.BadRequest{
            FieldViolations: []*errdetails.BadRequest_FieldViolation{
                {
                    Field:       "name",
                    Description: "len(name) must be [1, 32]",
                },
            },
        }))
}

Documentation

Overview

Code generated by genhelper.go; DO NOT EDIT.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Aborted

func Aborted(msg string, aps ...AppendDetail) *status.Status

func AbortedError

func AbortedError(msg string, aps ...AppendDetail) error

func AlreadyExists

func AlreadyExists(msg string, aps ...AppendDetail) *status.Status

func AlreadyExistsError

func AlreadyExistsError(msg string, aps ...AppendDetail) error

func Canceled

func Canceled(msg string, aps ...AppendDetail) *status.Status

func CanceledError

func CanceledError(msg string, aps ...AppendDetail) error

func DataLoss

func DataLoss(msg string, aps ...AppendDetail) *status.Status

func DataLossError

func DataLossError(msg string, aps ...AppendDetail) error

func DeadlineExceeded

func DeadlineExceeded(msg string, aps ...AppendDetail) *status.Status

func DeadlineExceededError

func DeadlineExceededError(msg string, aps ...AppendDetail) error

func FailedPrecondition

func FailedPrecondition(msg string, aps ...AppendDetail) *status.Status

func FailedPreconditionError

func FailedPreconditionError(msg string, aps ...AppendDetail) error

func Internal

func Internal(msg string, aps ...AppendDetail) *status.Status

func InternalError

func InternalError(msg string, aps ...AppendDetail) error

func InvalidArgument

func InvalidArgument(msg string, aps ...AppendDetail) *status.Status

func InvalidArgumentError

func InvalidArgumentError(msg string, aps ...AppendDetail) error

func NotFound

func NotFound(msg string, aps ...AppendDetail) *status.Status

func NotFoundError

func NotFoundError(msg string, aps ...AppendDetail) error

func OutOfRange

func OutOfRange(msg string, aps ...AppendDetail) *status.Status

func OutOfRangeError

func OutOfRangeError(msg string, aps ...AppendDetail) error

func PermissionDenied

func PermissionDenied(msg string, aps ...AppendDetail) *status.Status

func PermissionDeniedError

func PermissionDeniedError(msg string, aps ...AppendDetail) error

func ResourceExhausted

func ResourceExhausted(msg string, aps ...AppendDetail) *status.Status

func ResourceExhaustedError

func ResourceExhaustedError(msg string, aps ...AppendDetail) error

func Unauthenticated

func Unauthenticated(msg string, aps ...AppendDetail) *status.Status

func UnauthenticatedError

func UnauthenticatedError(msg string, aps ...AppendDetail) error

func Unavailable

func Unavailable(msg string, aps ...AppendDetail) *status.Status

func UnavailableError

func UnavailableError(msg string, aps ...AppendDetail) error

func Unimplemented

func Unimplemented(msg string, aps ...AppendDetail) *status.Status

func UnimplementedError

func UnimplementedError(msg string, aps ...AppendDetail) error

func Unknown

func Unknown(msg string, aps ...AppendDetail) *status.Status

func UnknownError

func UnknownError(msg string, aps ...AppendDetail) error

Types

type AppendDetail

type AppendDetail func(*status.Status) *status.Status

func BadRequest

func BadRequest(ri *errdetails.BadRequest) AppendDetail

func DebugInfo

func DebugInfo(ri *errdetails.DebugInfo) AppendDetail

func ErrorInfo

func ErrorInfo(ri *errdetails.ErrorInfo) AppendDetail

func Help

func Help(ri *errdetails.Help) AppendDetail

func LocalizedMessage

func LocalizedMessage(ri *errdetails.LocalizedMessage) AppendDetail

func QuotaFailure

func QuotaFailure(ri *errdetails.QuotaFailure) AppendDetail

func RequestInfo

func RequestInfo(ri *errdetails.RequestInfo) AppendDetail

func ResourceInfo

func ResourceInfo(ri *errdetails.ResourceInfo) AppendDetail

func RetryInfo

func RetryInfo(ri *errdetails.RetryInfo) AppendDetail

type GRPCStatus

type GRPCStatus interface {
	GRPCStatus() *status.Status
}

type Translated

type Translated interface {
	TranslatedStatus() *status.Status
}

type TranslatedError

type TranslatedError struct {
	Translated *status.Status
	Original   error
}

func AsIs

func AsIs(st *status.Status) *TranslatedError

func Translate

func Translate(err error, st *status.Status) *TranslatedError

func (*TranslatedError) Error

func (te *TranslatedError) Error() string

func (*TranslatedError) GRPCStatus

func (te *TranslatedError) GRPCStatus() *status.Status

func (*TranslatedError) TranslatedStatus

func (te *TranslatedError) TranslatedStatus() *status.Status

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL