tutorial

20 Menambahkan Interceptor Unary di Client

Ketika membangun aplikasi distributed system dengan gRPC, sering kali kita membutuhkan logging, monitoring, validasi, atau transformasi data pada request/response yang lewat antara client dan server. Namun, tentunya tidak bijak jika setiap logika tambahan itu kita selipkan pada tiap pemanggilan RPC. Untungnya, gRPC menyediakan konsep interceptor yang sangat powerful dan modular. Salah satu yang paling sering digunakan adalah Unary Interceptor di sisi client.

Artikel ini akan membahas secara mendalam apa, kenapa, dan bagaimana menambahkan Unary Interceptor di gRPC client dengan contoh pada bahasa Go. Kita akan membedah kasus penggunaan umum, menampilkan kode, serta melakukan simulasi dan visualisasi data flow dengan diagram.


Apa itu Unary Interceptor di Client?

Secara sederhana, interceptor adalah middleware (atau hook) yang bisa kita selipkan ke dalam life-cycle pemanggilan RPC. Pada unary RPC (RPC yang satu request satu response), unary interceptor berfungsi mengintersepsi tiap request outgoing ke server sebelum benar-benar terjadi serta memperlakukan response sebelum akhirnya diberikan ke aplikasi.

Menggunakan interceptor memungkinkan kita untuk:

  • Logging request dan response
  • Mengatur retry, timeout, deadline
  • Validasi data sebelum dikirim
  • Melakukan tracing (OpenTelemetry, Jaeger, dll.)
  • Menangani authentication/sekuritas (menambah header, dsb)
  • Custom error handling

Karena sifatnya yang reusable, kode menjadi lebih rapih, maintainable, dan DRY (Don’t Repeat Yourself).


Skema Alur Unary Interceptor

Mari lihat dulu bagaimana data mengalir saat kita gunakan unary interceptor pada client.

graph LR
    A[Application Code] --> B[Unary Interceptor]
    B --> C[gRPC Client Stub]
    C --> D[gRPC Server]
    D --> C
    C --> B
    B --> A
  • A: Code aplikasi yang memanggil RPC.
  • B: Interceptor, intercept request (dan response).
  • C: Library/stub gRPC.
  • D: Server gRPC.

Interceptor akan dipanggil setiap kali ada call ke method gRPC apa pun. Di dalam interceptor, kita bisa melakukan logic tambahan sebelum/atau sesudah invoker() (handler sebenarnya).


Cara Menambahkan Unary Interceptor di Client (Go)

1. Persiapan

Pastikan Anda punya project gRPC dan dependency berikut:

go get google.golang.org/grpc

Kita asumsikan sudah terdapat stub hasil generate proto (misal: pb.MyServiceClient).

2. Implementasi Unary Interceptor

Sintaks general unary interceptor di Go seperti berikut:

func MyUnaryClientInterceptor(
    ctx context.Context,
    method string,
    req, reply interface{},
    cc *grpc.ClientConn,
    invoker grpc.UnaryInvoker,
    opts ...grpc.CallOption,
) error {
    // Logic sebelum request
    err := invoker(ctx, method, req, reply, cc, opts...)
    // Logic setelah request
    return err
}
  • invoker() adalah call sebenarnya ke service. Anda wajib memanggilnya, kecuali memang ingin menolak kirm request ke server.
  • Bisa tempatkan logic sebelum (pre), sesudah (post), atau malah mem-block/passthrough.

Contoh: Logging Interceptor

Misal, kita ingin mencatat waktu request dan respon tiap kali client memanggil RPC method.

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

func LoggingUnaryClientInterceptor(
    ctx context.Context,
    method string,
    req, reply interface{},
    cc *grpc.ClientConn,
    invoker grpc.UnaryInvoker,
    opts ...grpc.CallOption,
) error {
    start := time.Now()
    log.Printf("[Client Interceptor] >> Outgoing request: %s", method)
    err := invoker(ctx, method, req, reply, cc, opts...) // kirim request
    elapsed := time.Since(start)
    log.Printf("[Client Interceptor] << Response from %s in %s Err:%v", method, elapsed, err)
    return err
}

3. Attach Interceptor ke Client

Pada gRPC Go, unary interceptor di-attach pada dial option saat membuat koneksi client.

conn, err := grpc.Dial(
    "localhost:50051",
    grpc.WithInsecure(),
    grpc.WithUnaryInterceptor(LoggingUnaryClientInterceptor),
)
if err != nil {
    log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
client := pb.NewMyServiceClient(conn)

Setelah ini, setiap RPC unary yang dipanggil oleh client secara otomatis akan melewati middleware LoggingUnaryClientInterceptor yang kita definisikan.


Studi Kasus: Menambah Authentication Token

Anggap kita ingin menyisipkan header auth token di setiap request, supaya server bisa mengenali identitas klien.

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

func AuthTokenUnaryClientInterceptor(token string) grpc.UnaryClientInterceptor {
    return func(
        ctx context.Context,
        method string,
        req, reply interface{},
        cc *grpc.ClientConn,
        invoker grpc.UnaryInvoker,
        opts ...grpc.CallOption,
    ) error {
        // Sisipkan metadata header auth
        md := metadata.Pairs("authorization", "Bearer "+token)
        ctx = metadata.NewOutgoingContext(ctx, md)
        return invoker(ctx, method, req, reply, cc, opts...)
    }
}

Attach pada waktu dialing:

conn, err := grpc.Dial(
    "localhost:50051",
    grpc.WithInsecure(),
    grpc.WithUnaryInterceptor(AuthTokenUnaryClientInterceptor("token123")),
)

Chaining Multiple Interceptor

Pada Go 1.36+, gunakan third-party untuk chaining (mis: grpc_middleware). Contoh:

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

conn, err := grpc.Dial(
    "localhost:50051",
    grpc.WithUnaryInterceptor(
        grpc_middleware.ChainUnaryClient(
            LoggingUnaryClientInterceptor,
            AuthTokenUnaryClientInterceptor("token123"),
        ),
    ),
)

Setiap interceptor dijalankan sesuai urutannya dalam chain.


Simulasi Output

Bayangkan client memanggil satu method GetUser(ctx, req) ke server. Dengan dua interceptor: Logging dan Auth.

Tabel Simulasi Proses:

StepModuleActionDetail
1App CodeCall GetUser()
2Logging Int.Catat waktu mulai, log outgoing methodGetUser
3Auth Int.Sisipkan Auth header ke outgoing contextHeader: Bearer token123
4gRPC stubKirimkan request ke server
5Logging Int.Hitung waktu, log responseTampilkan latency


Best Practice

  1. Selalu panggil invoker() di dalam interceptor agar request tetap berjalan.
  2. Pisahkan logic ke beberapa interceptor sesuai concern (logging, auth, tracing).
  3. Pastikan urutan interceptor sesuai kebutuhan (misal: inject header sebelum logging/after).
  4. Gunakan chain/stack bila perlu sehingga mudah dipelihara dan scalable.
  5. Testing interceptor secara unit & integration untuk memastikan tidak mengubah behavior aplikasi secara tidak sengaja.

Kesimpulan

Menambahkan unary interceptor di client gRPC adalah teknik yang sangat powerful untuk menjaga kode clean, DRY, dan scalable meskipun aplikasi semakin kompleks. Baik itu untuk logging, monitoring, auth, atau apapun, interceptor menawarkan satu tempat sentral untuk mengembangkan behavior aplikasi secara modular.

Jika Anda baru mulai menggunakan gRPC, cobalah implementasi di atas dan lihat betapa mudahnya memperkaya behavior client tanpa harus mengubah satu per satu call Anda.

Jangan ragu untuk bereksperimen dengan use-case lain menggunakan dasar interceptor ini, dan happy coding!


Referensi

comments powered by Disqus

Topik Terhangat

programming
176
tips-and-trick
43
tutorial
43
jaringan
28
hardware
11
linux
4
kubernetes
1