tutorial

40 Chain Interceptor dengan grpc_middleware

Di dunia pengembangan microservices modern, gRPC menjadi pilihan utama berkat performanya yang tinggi, format pesan yang kompak, dan dukungan polyglot yang mumpuni. Namun, serupa dengan REST, arsitektur layanan berbasis gRPC juga membutuhkan mekanisme middleware untuk menangani fitur seperti logging, authentication, validation, hingga rate limiting secara konsisten dan reusable.

Dalam ekosistem Golang, library grpc_middleware hadir sebagai solusi utama untuk merangkai interceptor dalam bentuk rantai (chain interceptor). Artikel ini membedah konsep chain interceptor pada gRPC menggunakan grpc_middleware, dilengkapi contoh kode, simulasi sederhana, serta penjelasan visual menggunakan diagram mermaid.


Apa Itu gRPC Interceptor?

Sebelum memahami chaining, mari singgung ulang: interceptor pada gRPC adalah “middleware” — kode yang dieksekusi sebelum/atau sesudah handler utama pada suatu RPC. Ada dua jenis interceptor:

  • UnaryInterceptor: Untuk unary RPC (permintaan-respons tunggal)
  • StreamInterceptor: Untuk streaming RPC (request/response bertipe stream)

Aplikasi nyata interceptor antara lain:

  • Logging otomatis setiap RPC
  • Validasi Header/Token
  • Rate limiting
  • Monitoring dan instrumentasi (Prometheus, OpenTracing)

Tantangan: Middleware Bertumpuk

Pada aplikasi skala besar, satu endpoint RPC bisa menjalankan banyak “middleware” berurutan. Menulis semuanya langsung di handler menjadi anti-pattern: sulit di-maintain dan di-reuse.

Di sini, grpc_middleware menjadi game changer: menyediakan mekanisme chain yang elegan, mirip Express.js di Node.js.


Instalasi & Setup Awal

Pastikan Anda sudah memiliki modul berikut:

go get github.com/grpc-ecosystem/go-grpc-middleware

Paket ini menawarkan helper untuk chaining unary dan stream interceptor.


Contoh: Membangun Chain Interceptor

Mari kita bangun tiga interceptor sederhana:

  1. Logging Interceptor: Menampilkan method RPC yang dipanggil
  2. Auth Interceptor: Memeriksa key sederhana di metadata
  3. Validation Interceptor: Memastikan request tidak kosong

1. Contoh Interceptor

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "fmt"
    "errors"
)

// Logging Interceptor
func LoggingInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    fmt.Println("[LOG] gRPC called:", info.FullMethod)
    return handler(ctx, req)
}

// Auth Interceptor
func AuthInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok || len(md["x-api-key"]) == 0 || md["x-api-key"][0] != "secret123" {
        return nil, errors.New("unauthorized")
    }
    return handler(ctx, req)
}

// Validation Interceptor (dummy)
func ValidateInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    if req == nil {
        return nil, errors.New("empty request")
    }
    return handler(ctx, req)
}

2. Chaining dengan grpc_middleware

Dengan grpc_middleware, kita compose interceptor di atas menjadi satu rantai:

import "github.com/grpc-ecosystem/go-grpc-middleware"

// ... interceptor definitions ...

server := grpc.NewServer(
    grpc_middleware.WithUnaryServerChain(
        LoggingInterceptor,
        AuthInterceptor,
        ValidateInterceptor,
    ),
)

Diagram Alur: Unary Chain Interceptor

flowchart LR
    A[gRPC Client] --> |RPC call| B[LoggingInterceptor]
    B --> |pass| C[AuthInterceptor]
    C --> |pass| D[ValidateInterceptor]
    D --> |pass| E[Actual Handler]
    E --> |response| D
    D --> |return| C
    C --> |return| B
    B --> |return| A

Penjelasan:

  • Setiap interceptor memiliki akses ke handler selanjutnya, membentuk rantai.
  • Jika di satu titik error terjadi (misal Auth gagal), proses “dipotong” dan error dikembalikan ke client.

Simulasi: Urut-Urutan Eksekusi Interceptor

Misalkan kita punya service method SayHello, yang menerima request kosong dan metadata x-api-key.

1. Client Request BENAR

  • Metadata: x-api-key: secret123
  • Request: {} (tidak nil)

Eksekusi:

  • LoggingInterceptor: mencatat log
  • AuthInterceptor: API Key benar
  • ValidateInterceptor: request valid
  • Handler: dieksekusi

2. Client Request API Key SALAH

  • Metadata: none
  • Request: {}

Eksekusi:

  • LoggingInterceptor: mencatat log
  • AuthInterceptor: API Key invalid → STOP, return error unauthenticated

3. Client Request NIL

  • Metadata: x-api-key: secret123
  • Request: nil

Eksekusi:

  • LoggingInterceptor: mencatat log
  • AuthInterceptor: API Key valid
  • ValidateInterceptor: request nil → STOP, return error

Simulasi Output

SkenarioLoggingAuthValidationHandlerOutput
API key benar, req okYaYaYaYaSukses (Hello resp.)
API key SALAHYaError--Error unauthorized
Request NILYaYaError-Error empty request

Keterangan:

  • Panah berhenti di kolom error, handler tidak dipanggil.
  • Middleware dapat memotong atau meneruskan eksekusi.

Keunggulan Chain Interceptor

  1. Reusable
    Setiap interceptor bisa digunakan lintas service tanpa copy-paste.

  2. Order Control
    Urutan eksekusi jelas & mudah diatur (mirip middleware stack pada web framework).

  3. Composable
    Mudah injeksi fitur baru (audit, tracing, circuit breaker, dll).

  4. Separation of Concern
    Handler RPC tetap fokus pada business logic.


Chaining Stream Interceptor

Selain unary, grpc_middleware mendukung stream dengan API identik:

server := grpc.NewServer(
    grpc_middleware.WithStreamServerChain(
        MyStreamLoggingInterceptor,
        AuthStreamInterceptor,
    ),
)

Handler stream mengikuti signature berbeda, tapi prinsip chaining tetap sama.


Tips Best Practice

  • Susun dari yang bersifat universal ke spesifik (contoh: logging → auth → custom)
  • Interceptor harus idempotent dan tidak mengubah request sembarangan
  • Error handling jelas, jangan “panic” dalam interceptor
  • Gunakan context untuk passing value antar interceptor jika perlu

Kesimpulan

Dengan chain interceptor dari grpc_middleware, aplikasi gRPC di Golang menjadi modular, scalable, dan maintainable. Seperti rangkaian filter, kita bisa mengontrol aliran request ke handler, baik untuk alasan keamanan, logging, maupun observasi metrik.

Berinvestasilah pada desain interceptor sejak awal—karena semakin berkembang layanan Anda, semakin penting layer middleware yang tersusun rapi. Selamat mencoba dan scale up gRPC Anda secara profesional!


Referensi:

Happy coding! 🚀

comments powered by Disqus