tutorial

17 Menambahkan Metadata pada gRPC Request

gRPC telah menjadi standar de facto untuk komunikasi service-to-service di banyak perusahaan yang mengadopsi microservices. Sifatnya yang efisien membuatnya sangat cocok untuk sistem berskala besar. Namun, dalam praktik, kebutuhan akan komunikasi yang lebih “kontekstual” seringkali muncul—misal, pengiriman token otentikasi, trace id, atau custom header di setiap request. Disinilah pentingnya metadata pada gRPC: memberikan medium ekstra untuk mengirim data luar-band tanpa memodifikasi definisi protokol utama.

Pada artikel kali ini, saya akan membahas tuntas metadata pada gRPC request, mulai teori dasar, simulasi arsitektur, hingga contoh kode di Node.js dan Go. Tidak hanya contoh, saya juga akan share praktik terbaik dan kendala yang perlu diwaspadai. Yuk, kita mulai!


Apa Itu Metadata pada gRPC?

Serupa HTTP header, metadata pada gRPC adalah pasangan key-value yang dikirim sebagai bagian dari setiap permintaan atau respons RPC. Mereka digunakan untuk informasi tambahan seperti:

  • Token otentikasi
  • Trace/Correlation ID untuk observabilitas
  • Opsi konten
  • Info tenant (multi tenant apps)
  • Dan informasi eksternal lain yang tidak ingin ‘mencemari’ payload utama

Diagram Alur Metadata pada gRPC

Mari kita visualisasikan alurnya lewat diagram mermaid berikut:

sequenceDiagram
    participant Client
    participant Network
    participant Server

    Client->>Network: gRPC Request (Metadata + Payload)
    Network->>Server: gRPC Request (Metadata + Payload)
    Server-->>Network: gRPC Response (Metadata + Payload)
    Network-->>Client: gRPC Response (Metadata + Payload)

Seperti terlihat, baik request maupun response dapat membawa metadata.


Use Case Nyata: Skenario “User Authentication”

Salah satu skenario paling umum adalah passing token JWT (JSON Web Token) pada setiap request sebagai bukti otentikasi. Di gRPC, ini tidak dilakukan lewat parameter di protokol, tapi dengan header/metadata.


Contoh Skema Protobuf

Misal kita punya service berikut:

// protos/user.proto
syntax = "proto3";

service UserService {
    rpc GetProfile(Empty) returns (UserProfile);
}

message Empty {}

message UserProfile {
    string user_id = 1;
    string name = 2;
    string email = 3;
}

Tidak ada kolom token auth, karena akan dikirim lewat metadata.


Implementasi gRPC Metadata

Mari lihat implementasi di dua bahasa populer:

1. Node.js (menggunakan @grpc/grpc-js)

a) Client: Mengirim Request Bersama Metadata

const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');

// Load proto
const packageDefinition = protoLoader.loadSync('./protos/user.proto');
const userProto = grpc.loadPackageDefinition(packageDefinition).UserService;

// Buat client
const client = new userProto('localhost:50051', grpc.credentials.createInsecure());

// Buat metadata
const metadata = new grpc.Metadata();
metadata.add('authorization', 'Bearer eyJhbGciOiJI...');  // JWT misalnya

// Call RPC dengan metadata
client.GetProfile({}, metadata, (err, response) => {
  if (err) console.error(err);
  else console.log(response);
});

b) Server: Membaca Metadata Request

const grpc = require('@grpc/grpc-js');

function getProfile(call, callback) {
  // Ambil nilai 'authorization'
  const authToken = call.metadata.get('authorization')[0];
  console.log('Received JWT:', authToken);

  // TODO: Verifikasi token
  // Simulasikan user profile
  if (authToken) {
    callback(null, {
      user_id: 'u-123',
      name: 'Rizky',
      email: 'rizky@example.com'
    });
  } else {
    callback({
      code: grpc.status.UNAUTHENTICATED,
      message: 'Auth token required'
    });
  }
}

2. Go: Server & Client

a) Client: Mengirim Metadata

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

func main() {
    conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
    defer conn.Close()
    client := pb.NewUserServiceClient(conn)

    // Buat metadata
    md := metadata.New(map[string]string{"authorization": "Bearer abcd1234"})
    ctx := metadata.NewOutgoingContext(context.Background(), md)

    // RPC call dgn metadata
    resp, err := client.GetProfile(ctx, &pb.Empty{})
    // Handle resp / err
}

b) Server Handler: Membaca Metadata

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

func (s *server) GetProfile(ctx context.Context, req *pb.Empty) (*pb.UserProfile, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        return nil, status.Error(codes.Unauthenticated, "No metadata found")
    }
    tokens := md["authorization"]
    if len(tokens) == 0 {
        return nil, status.Error(codes.Unauthenticated, "No authorization token")
    }
    // Verifikasi token, dst
    return &pb.UserProfile{
        UserId: "u-456",
        Name: "Dewi",
        Email: "dewi@example.com",
    }, nil
}

Tipe Metadata: Default vs Custom

Metadata KeyContohPenjelasan
authorizationBearer eyJhbG…Token JWT/OAuth2
x-trace-id3f4a…Correlation untuk tracing
localeid-IDKustomisasi multi-bahasa
content-typeapplication/grpcBiasanya dikelola otomatis

Catatan:

  • Key standard biasanya lowercase semua.
  • Setiap “binary” value (bukan string) harus diberi suffix -bin.

Best Practice Penggunaan Metadata

1. Jangan Overload Metadata

Metadata sangat berguna, namun pastikan tetap kecil—idealnya puluhan hingga ratusan bytes. Metadata terlalu besar dapat menurunkan performa dan merusak semantik layanan.

2. Standardisasi Key

Gunakan penamaan yang konsisten dan dokumentasikan key yang digunakan seluruh tim/layanan.

3. Minimal Satu-Way

Gunakan metadata berbeda untuk request dan response sesuai standar gRPC.
Misal, otentikasi client -> server, sedangkan error detail dalam response.

4. Jangan Simpan State Penting

Metadata bersifat stateless. Jangan gunakan untuk menyimpan informasi yang seharusnya ada di database atau session.

5. Interceptor/Middleware

Implementasikan pemeriksaan, logging, atau inject metadata lewat interceptor/middleware. Modular dan scalable.


Interceptor: Inject Otomatis di Setiap Request (Node.js Example)

function authInterceptor(options, nextCall) {
  return new grpc.InterceptingCall(nextCall(options), {
    start: function(metadata, listener, next) {
      // Inject token setiap request
      metadata.add('authorization', 'Bearer juragan_secret');
      next(metadata, listener);
    }
  });
}

// Gunakan di client
const client = new userProto('localhost:50051', grpc.credentials.createInsecure(), {
  interceptors: [authInterceptor]
});

Troubleshooting Umum

  • Metadata tidak sampai ke server: Cek key spelling, implementasi interceptor, dan perhatikan bahwa size metadata berlebihan bisa jadi ditolak middleware/network.
  • Metadata hilang di stream (bidirectional): Beberapa implementasi perlu memperhatikan initial vs trailing metadata.
  • Kesalahan binary header: Key dengan suffix -bin wajib untuk data buffer/binary, sebaliknya akan terjadi error parsing.

Kesimpulan

Dengan pemahaman dan best practice seputar metadata, layanan gRPC Anda bisa lebih aman, traceable, dan future-proof. Komunikasi service-to-service yang membawa context kini menjadi nyata tanpa harus “merusak” protokol, hanya dengan sentuhan metadata.

Jangan lupa untuk menguji skenario edge case dan buat util/interceptor untuk konsistensi pengiriman dan eksekusi metadata. Metadata bukan cuma “header”, tapi juga kunci interoperabilitas dan observabilitas pada ekosistem distributed system modern.

Sumber Lain & Referensi:


Selamat mencoba, semoga service-mu makin robust dan scalable! 🚀

comments powered by Disqus

Topik Terhangat

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