tutorial

32 Menulis Unary Interceptor Sendiri

Ketika membangun servis gRPC di ekosistem Go, sangat sering kita dihadapkan pada kebutuhan untuk memasang fungsi middleware: entah itu logging request, validasi autentikasi, audit, monitoring, hingga pengelolaan error dengan pola yang konsisten. Di dunia REST, hal seperti ini jamak dilakukan lewat middleware seperti pada Gin atau Echo. Sementara di gRPC, konsep yang setara disebut interceptor.

Pada artikel ini, saya akan membahas secara praktis tentang Unary Interceptor pada gRPC: mulai dari pemahaman konsep, arsitektur, implementasi, hingga tips produksi. Kita juga akan langsung membuat sebuah custom unary interceptor dari awal, lengkap dengan contoh kode, simulasi, dan diagram alur.


Apa Itu Unary Interceptor?

Interceptor pada gRPC adalah fungsi yang “menyela” proses permintaan (request) dari client ke server, sebelum method handler dieksekusi. Tipe utama interceptor di gRPC ada dua:

  1. Unary interceptor – Untuk method unary (satu request, satu response).
  2. Stream interceptor – Untuk streaming method (client, server, atau bidi).

Pada artikel ini kita akan fokus pada unary interceptor.

Mengapa Perlu Menggunakan Unary Interceptor?

Bayangkan Anda ingin memastikan semua endpoint API Anda diaudit, atau ingin tiap request di-log, atau validasi JWT dilakukan sebelum masuk ke logic utama. Jika menulis kode itu berulang-ulang di tiap handler, kode menjadi tidak reusable dan rawan bug. Di sinilah unary interceptor jadi solusi.

Sederhananya, dengan unary interceptor, kita punya satu tempat di mana logika tambahan dapat “memotong” semua request, baik sebelum atau sesudah handler utama dijalankan.


Diagram Alur Unary Interceptor

Mari kita visualisasikan prosesnya dengan flowchart menggunakan Mermaid:

flowchart TD
    A(Client gRPC Request) --> B[Unary Interceptor]
    B --> C{Pre-Processing}
    C -->|Pass| D["Handler (Your Actual gRPC Service)"]
    D --> E{Post-Processing}
    E --> F[gRPC Response]
    C -->|Blocked/Error| G[Return Error to Client]

Anatomy dari Unary Interceptor di Go

Mari kita lihat bentuk signature dari unary interceptor pada Go:

func(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (resp interface{}, err error)

Penjelasan parameter:

  • ctx: Context dari request, bisa dipakai untuk propagasi nilai custom (misal: user claim).
  • req: Payload dari request.
  • info: Berisi meta-informasi (fully qualified method name, dsb).
  • handler: Fungsi yang mengeksekusi handler sebenarnya.

Biasanya, kita menjalankan pre-processing, lalu memanggil handler(ctx, req), dan menambah post-processing jika perlu.


Contoh Implementasi Unary Interceptor: Logging

Untuk memulai, mari buat interceptor sederhana yang me-log tiap request masuk, lengkap dengan waktu eksekusinya.

import (
    "context"
    "log"
    "time"
    "google.golang.org/grpc"
)

func LoggingUnaryInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    start := time.Now()
    log.Printf("gRPC method: %s - req: %+v", info.FullMethod, req)

    resp, err := handler(ctx, req)

    log.Printf(
        "gRPC method: %s - resp: %+v - err: %v - duration: %s",
        info.FullMethod, resp, err, time.Since(start),
    )
    return resp, err
}

Cara Memasang Interceptor di Server

Setelah interceptor selesai, kita pasang saat membuat gRPC server:

server := grpc.NewServer(
    grpc.UnaryInterceptor(LoggingUnaryInterceptor),
)

Bisa juga chain beberapa unary interceptor sekaligus menggunakan grpc_middleware:

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

server := grpc.NewServer(
    grpc_middleware.WithUnaryServerChain(
        AuthUnaryInterceptor,
        LoggingUnaryInterceptor,
        MetricsUnaryInterceptor,
    ),
)

Simulasi: Membandingkan Sebelum & Sesudah Interceptor

Berikut simulasi proses request dengan dan tanpa unary interceptor:

Tanpa InterceptorDengan Interceptor
1gRPC Request MasukgRPC Request Masuk
2Handler dijalankanPre-processing oleh Interceptor
3Handler dijalankanHandler dijalankan
4Response dikirim ke klienPost-processing oleh Interceptor
5Response dikirim ke klien


Studi Kasus: Auth Interceptor dengan JWT

Mari kita buat sebuah interceptor yang melakukan validasi token JWT dari metadata sebelum handler dieksekusi. Bila token tidak valid, langsung return error; jika sah, propagate user claim ke context.

Implementasi:

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

func AuthUnaryInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, errors.New("missing metadata")
    }

    // Ambil Authorization header
    tokens := md["authorization"]
    if len(tokens) == 0 {
        return nil, errors.New("missing authorization token")
    }
    
    token := strings.TrimPrefix(tokens[0], "Bearer ")

    // Validasi token di sini (pseudocode)
    claims, err := ValidateJWT(token)
    if err != nil {
        return nil, errors.New("invalid token")
    }

    // Propagate claim ke context
    newCtx := context.WithValue(ctx, "user", claims)

    // Teruskan ke handler utama dengan context baru
    return handler(newCtx, req)
}

Handler Dapat Mengakses Claim User

user := ctx.Value("user").(UserClaims)
// sekarang user sudah bisa diakses pada handler

Best Practices dalam Menulis Unary Interceptor

  1. Sederhanakan logika: Pastikan tidak terlalu berat di dalam interceptor agar latency tidak bengkak.
  2. Tangani error secara eksplisit: Jika terjadi error, pastikan return error yang informatif.
  3. Propagasi context dengan baik: Insert apapun ke context dengan jelas agar handler bisa mengakses.
  4. Pisahkan urusan pre dan post processing: Pisahkan logika sebelum dan sesudah handler dijalankan.
  5. Chain interceptor sesuai urutan: Misal: Auth → Logging → Metrics.

Memahami Chain dan Komposisi Interceptor

Salah satu kelebihan interceptor adalah mudah dikomposisi. Dengan chain, urutan eksekusi bisa digambarkan sebagai berikut:

sequenceDiagram
    participant Client
    participant Interceptor1
    participant Interceptor2
    participant Handler
    
    Client->>Interceptor1: Request
    Interceptor1->>Interceptor2: Pre-processing
    Interceptor2->>Handler: Pre-processing
    Handler-->>Interceptor2: Response
    Interceptor2-->>Interceptor1: Post-processing
    Interceptor1-->>Client: Return Response

Kesimpulan

Unary interceptor adalah fitur esensial untuk membangun gRPC service yang robust, concise, dan maintainable. Dengan memahami arsitektur dan pola implementasinya, kita bisa dengan mudah menambah fungsionalitas seperti logging, autentikasi, rate-limiting, hingga observability tanpa code duplication pada tiap handler.

Dengan contoh-contoh dan studi kasus tadi, Anda sekarang siap menulis unary interceptor sendiri sesuai kebutuhan aplikasi Anda! Jangan lupa, desain interceptor dengan efisiensi dan clarity yang baik untuk menghasilkan servis-produk yang handal dan scalable.


Referensi:

Salam refactor!

comments powered by Disqus