インターセプター
Complete guide to interceptors and middleware in Goa - type-safe Goa interceptors, HTTP middleware, and gRPC interceptors.
Goa は、型安全なインターセプターと伝統的なミドルウェアパターンを組み合わせた、 リクエスト処理のための包括的なソリューションを提供します。このガイドでは 3 つのアプローチすべてを扱います。
概要
Goa サービスでリクエストを処理するとき、3 つの補完的なツールがあります:
- Goa インターセプター:型安全で、コンパイル時にサービスのドメイン型へのアクセスをチェックします。
- HTTPミドルウェア:HTTP特有の懸念に対する標準的な
http.Handlerパターン - gRPC インターセプター:RPC固有のニーズに対する標準gRPCパターン
それぞれを使用する場合
| 懸念事項 | ツール |
|---|---|
| ビジネスロジックの検証 | Goa インターセプター |
| データ変換 | Goa インターセプター |
| リクエスト/レスポンスのエンリッチメント | Goa インターセプター |
| ログ、トレース | HTTP/gRPC ミドルウェア |
| 圧縮、CORS | HTTP ミドルウェア |
| メタデータ処理 | gRPC インターセプター |
| レート制限 | HTTP/gRPC ミドルウェア |
Goa インターセプター
Goa インターセプターは、コンパイル時のチェックと生成されたヘルパーメソッドによって、 サービスのドメイン型への型安全なアクセスを提供します。
インターセプターの定義
var RequestLogger = Interceptor("RequestLogger", func() {
Description("Logs incoming requests and their timing")
// Read status from result
ReadResult(func() {
Attribute("status")
})
// Add timing information to result
WriteResult(func() {
Attribute("processedAt")
Attribute("duration")
})
})
インターセプターの適用
サービスまたはメソッドレベルで適用する:
var _ = Service("calculator", func() {
// Apply to all methods
ServerInterceptor(RequestLogger)
Method("add", func() {
// Method-specific interceptor
ServerInterceptor(ValidateNumbers)
Payload(func() {
Attribute("a", Int)
Attribute("b", Int)
})
Result(func() {
Attribute("sum", Int)
Attribute("status", Int)
Attribute("processedAt", String)
Attribute("duration", Int)
})
})
})
インターセプターの実装
func (i *ServerInterceptors) RequestLogger(ctx context.Context, info *RequestLoggerInfo, next goa.Endpoint) (any, error) {
start := time.Now()
// Call next interceptor or endpoint
res, err := next(ctx, info.RawPayload())
if err != nil {
return nil, err
}
// Access result through type-safe interface
r := info.Result(res)
// Add timing information
r.SetProcessedAt(time.Now().Format(time.RFC3339))
r.SetDuration(int(time.Since(start).Milliseconds()))
return res, nil
}
アクセスパターン
読み取り専用アクセス
var Monitor = Interceptor("Monitor", func() {
Description("Collects metrics without modifying data")
ReadPayload(func() {
Attribute("size")
})
ReadResult(func() {
Attribute("status")
})
})
書き込みアクセス
var Enricher = Interceptor("Enricher", func() {
Description("Adds context information")
WritePayload(func() {
Attribute("requestID")
})
WriteResult(func() {
Attribute("processedAt")
})
})
複合アクセス
var DataProcessor = Interceptor("DataProcessor", func() {
Description("Processes both requests and responses")
ReadPayload(func() {
Attribute("rawData")
})
WritePayload(func() {
Attribute("processed")
})
ReadResult(func() {
Attribute("status")
})
WriteResult(func() {
Attribute("enriched")
})
})
クライアント側のインターセプター
var ClientContext = Interceptor("ClientContext", func() {
Description("Enriches requests with client context")
WritePayload(func() {
Attribute("clientVersion")
Attribute("clientID")
})
ReadResult(func() {
Attribute("rateLimit")
Attribute("rateLimitRemaining")
})
})
var _ = Service("inventory", func() {
ClientInterceptor(ClientContext)
// ...
})
ストリーミングインターセプター
ストリーミング・メソッドには、ストリーミング・バリアントを使います:
var ServerProgressTracker = Interceptor("ServerProgressTracker", func() {
Description("Adds progress to server stream responses")
WriteStreamingResult(func() {
Attribute("percentComplete")
Attribute("itemsProcessed")
})
})
var ClientMetadataEnricher = Interceptor("ClientMetadataEnricher", func() {
Description("Enriches outgoing client stream messages")
WriteStreamingPayload(func() {
Attribute("clientTimestamp")
})
})
実行順序
1.サービスレベルのインターセプター(宣言順) 2.メソッドレベルのインターセプター(宣言順) 3.実際のエンドポイント 4.メソッドレベルのインターセプター(逆順) 5.サービスレベルのインターセプター(逆順)
HTTP ミドルウェア
HTTPミドルウェアは、標準的なhttp.Handlerパターンを使ってプロトコルレベルの懸念を処理します。
一般的なミドルウェアスタック
mux := goahttp.NewMuxer()
// Add middleware (outermost to innermost)
mux.Use(debug.HTTP()) // Debug logging
mux.Use(otelhttp.NewMiddleware("service")) // OpenTelemetry
mux.Use(log.HTTP(ctx)) // Request logging
mux.Use(goahttpmiddleware.RequestID()) // Request ID
mux.Use(goahttpmiddleware.PopulateRequestContext()) // Goa context
``` パターン
### カスタムミドルウェアの作成
```go
func ExampleMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Pre-processing
start := time.Now()
next.ServeHTTP(w, r)
// Post-processing
log.Printf("Request took %v", time.Since(start))
})
}
セキュリティヘッダーミドルウェア
func SecurityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-XSS-Protection", "1; mode=block")
next.ServeHTTP(w, r)
})
}
コンテキスト・エンリッチメント・ミドルウェア
func ContextEnrichmentMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, "request.start", time.Now())
ctx = context.WithValue(ctx, "request.id", r.Header.Get("X-Request-ID"))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
エラー処理ミドルウェア
func ErrorHandlingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic recovered: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
gRPCインターセプター
gRPCインターセプターは、RPCコールのプロトコルレベルの懸念を処理します。
単項インターセプター
func LoggingInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("Method: %s, Duration: %v, Error: %v",
info.FullMethod, time.Since(start), err)
return resp, err
}
}
ストリームインターセプタ
func StreamLoggingInterceptor() grpc.StreamServerInterceptor {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
start := time.Now()
err := handler(srv, ss)
log.Printf("Stream: %s, Duration: %v, Error: %v",
info.FullMethod, time.Since(start), err)
return err
}
}
Goaとの統合
func main() {
srv := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
MetadataInterceptor(),
LoggingInterceptor(),
MonitoringInterceptor(),
)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
StreamMetadataInterceptor(),
StreamLoggingInterceptor(),
)),
)
pb.RegisterServiceServer(srv, server)
}
3つの組み合わせ
3つのアプローチの組み合わせは以下の通りだ:
func main() {
// 1. Create service with Goa interceptors
svc := NewService()
interceptors := NewInterceptors(log.Default())
endpoints := NewEndpoints(svc, interceptors)
// 2. Set up HTTP with middleware
mux := goahttp.NewMuxer()
mux.Use(otelhttp.NewMiddleware("payment-svc"))
mux.Use(debug.HTTP())
mux.Use(log.HTTP(ctx))
httpServer := genhttp.New(endpoints, mux, dec, enc, eh, eh)
genhttp.Mount(mux, httpServer)
// 3. Set up gRPC with interceptors
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_recovery.UnaryServerInterceptor(),
grpc_prometheus.UnaryServerInterceptor,
)),
)
grpcSvr := gengrpc.New(endpoints, nil)
genpb.RegisterPaymentServer(grpcServer, grpcSvr)
}
実行フロー
Request Processing:
─────────────────────────────────────────────────────────────────>
HTTP/gRPC Middleware → Goa Interceptors → Service Method
Response Processing:
<─────────────────────────────────────────────────────────────────
Service Method → Goa Interceptors → HTTP/gRPC Middleware
ベストプラクティス
Goa インターセプター
- ビジネスロジックの検証とデータ変換に使用する
- インターセプターは単一の責務に集中させる
- 型安全なアクセスパターンを使用する
HTTP ミドルウェア
- ミドルウェアの順序は慎重に(パニック・リカバリを最初に、次にロギングなど)。
- 高価なオブジェクトは事前にコンパイルしておく(正規表現など)
- 頻繁に割り当てられるオブジェクトには sync.Pool を使用する。
gRPC インターセプター
- プロトコルレベルの懸念に焦点を当てる
- コンテキストキャンセルを適切に処理する
- 適切なステータスコードを使用する
一般
- インターセプター/ミドルウェアを分離してテストする
- パフォーマンスへの影響を考慮する
- 各インターセプターの目的を文書化する