このガイドでは、最初のGoaインターセプターの作成と使用方法について説明します。 メソッド呼び出しのタイミングを記録する簡単なロギングインターセプターを作成します。
インターセプターは設計内でInterceptor
関数を使用して定義します。以下は簡単な
ロギングインターセプターの例です:
var RequestLogger = Interceptor("RequestLogger", func() {
Description("受信リクエストとそのタイミングをログに記録します")
// リザルトからメソッドのステータスを読み取りたい
ReadResult(func() {
Attribute("status", Int, "返却されたステータスコード") // HTTPではなくビジネスロジックのステータスコード
})
// タイミング情報をリザルトに追加する
WriteResult(func() {
Attribute("processedAt", String, "リクエストが処理された時刻")
Attribute("duration", Int, "処理時間(ミリ秒)")
})
})
Interceptor
DSLはRequestLogger
という名前の新しいインターセプターを定義します。
ReadResult
とWriteResult
を使用して、リザルトからアクセスする必要のあるフィールドを
指定します - この場合、リザルトのステータスコードを読み取り、タイミング情報を書き込みます。
Goaにはペイロードの読み書きに対応するDSLもあります。
インターセプターはサービスレベルとメソッドレベルの両方に適用できます:
var _ = Service("calculator", func() {
// サービス内のすべてのメソッドに適用
ServerInterceptor(RequestLogger)
Method("add", func() {
// メソッド固有のインターセプター
ServerInterceptor(ValidateNumbers)
Payload(func() {
Attribute("a", Int)
Attribute("b", Int)
})
Result(Int)
})
})
この例は、ServerInterceptor
を使用してサービス設計でインターセプターを適用する方法を
示しています。サービスレベル(すべてのメソッドに影響)またはメソッドレベル(そのメソッド
のみに影響)で適用できます。
これにより、Goaの生成コードを通じて型安全性を維持しながら、サービス操作全体のタイミングを 追跡できる簡単なロギングシステムが作成されます。
生成されたコードは、インターセプターを実装するための型安全なインターフェースを提供します。 以下はロギングインターセプターの実装方法です:
func (i *ServerInterceptors) RequestLogger(ctx context.Context, info *RequestLoggerInfo, next goa.Endpoint) (any, error) {
start := time.Now()
// 次のインターセプターまたは最終エンドポイントを呼び出す
res, err := next(ctx, info.RawPayload())
if err != nil {
return nil, err
}
// 型安全なインターフェースを通じてリザルトにアクセス
r := info.Result(res)
// タイミング情報を追加
r.SetProcessedAt(time.Now().Format(time.RFC3339))
r.SetDuration(int(time.Since(start).Milliseconds()))
return res, nil
}
このインターセプターの動作を分解してみましょう:
関数シグネチャはGoaのインターセプターパターンに従います:
タイミングの取得:
start := time.Now()
リクエストの開始時刻を記録
次のハンドラーの呼び出し:
res, err := next(ctx, info.RawPayload())
リザルトへのアクセス:
r := info.Result(res)
生成された型安全なインターフェースを使用してリザルトにアクセス
タイミング情報の追加:
r.SetProcessedAt(time.Now().Format(time.RFC3339))
r.SetDuration(int(time.Since(start).Milliseconds()))
変更されたリザルトの返却:
return res, nil
強化されたレスポンスをチェーンの上位に渡す
インターセプターを定義すると、Goaはそれをサービスに組み込むために必要なコードを生成します。 生成されるコードの構造は以下の通りです:
ServerInterceptors
インターフェースを生成します:// ServerInterceptorsはすべてのサーバーサイドインターセプターのインターフェースを定義
type ServerInterceptors interface {
RequestLogger(ctx context.Context, info *RequestLoggerInfo, next goa.Endpoint) (any, error)
// ... 他のインターセプター ...
}
// 情報構造体はインターセプションに関するメタデータを提供
type RequestLoggerInfo struct {
service string
method string
callType goa.InterceptorCallType
rawPayload any
}
// リザルトにアクセスするための型安全なインターフェース
type RequestLoggerResult interface {
Status() int
SetProcessedAt(string)
SetDuration(int)
}
ServerInterceptors
インターフェースを実装します:type interceptors struct {
logger *log.Logger
}
func NewInterceptors(logger *log.Logger) *interceptors {
return &interceptors{logger: logger}
}
func (i *interceptors) RequestLogger(ctx context.Context, info *RequestLoggerInfo, next goa.Endpoint) (any, error) {
// 前の例からの実装
start := time.Now()
res, err := next(ctx, info.RawPayload())
if err != nil {
return nil, err
}
r := info.Result(res)
r.SetProcessedAt(time.Now().Format(time.RFC3339))
r.SetDuration(int(time.Since(start).Milliseconds()))
return res, nil
}
func main() {
// サービス実装を作成
svc := NewService()
// インターセプターを作成
interceptors := NewInterceptors(log.Default())
// インターセプター付きでエンドポイントを作成
endpoints := NewEndpoints(svc, interceptors)
// ... 通常通り続行 ...
}
生成されたコードは以下の主要な利点を提供します:
生成されたインターフェースとラッパーにより、インターセプターがチェーン全体を通じて型安全性を 維持しながら、リクエスト処理パイプラインに適切に統合されることが保証されます。
複数のインターセプターが適用される場合、以下の順序で実行されます:
これは、インターセプターがリクエストとレスポンスの両方のフローを包含することを意味します。
基本を理解したところで: