☀️Siang
Protokol

gRPC dan Protocol Buffers

Panduan lengkap gRPC — service definition dengan .proto files, unary dan streaming RPC, interceptors, error handling, metadata, dan performa tinggi untuk arsitektur microservices

Artikel: Grpc Protobuf Artikel: Grpc Protobuf

1. Apa Itu gRPC?

gRPC (gRPC Remote Procedure Call) adalah framework RPC high-performance yang dikembangkan oleh Google, menggunakan Protocol Buffers sebagai format serialisasi dan HTTP/2 sebagai transport. gRPC memungkinkan layanan berkomunikasi dengan efisien di arsitektur microservices.

gRPC vs REST

AspekREST/HTTPgRPC
FormatJSON (text, verbose)Protobuf (binary, compact)
TransportHTTP/1.1 (atau HTTP/2)HTTP/2 (wajib)
ContractOpenAPI/Swagger (opsional).proto file (wajib, strongly typed)
CodegenTidak nativeNative: Go, Java, Python, C++, Rust, dll.
StreamingSSE / WebSocket (terpisah)Built-in (4 mode)
Ukuran payloadBesar (JSON text)Kecil (binary, 3-10x lebih kecil)
SpeedRelatif lambat5-10x lebih cepat dari JSON
Browser supportNativePerlu gRPC-Web proxy
Use casePublic API, CRUDInternal microservices, real-time
💡 Kapan Menggunakan gRPC?

gRPC optimal untuk: komunikasi antar microservices (service-to-service), streaming real-time, IoT device communication, dan di mana performa dan ukuran payload kritis. REST tetap lebih baik untuk public API yang perlu diakses browser langsung atau oleh third-party yang beragam.

2. Protocol Buffers (Protobuf)

Protocol Buffers adalah mekanisme serialisasi data binary yang dikembangkan Google. Data didefinisikan dalam file .proto dengan schema yang strongly typed, kemudian di-generate menjadi kode di berbagai bahasa pemrograman.

Syntax Dasar .proto

Protobuf

// file: user.proto
syntax = "proto3";

package user;

option go_package = "github.com/myapp/proto/user";
option java_package = "com.myapp.proto";

import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";

// Message definition
message User {
  int32 id = 1;                    // Field number 1
  string name = 2;                 // Field number 2
  string email = 3;                // Field number 3
  UserRole role = 4;               // Enum field
  repeated string tags = 5;        // Repeated = array/list
  map<string, string> metadata = 6; // Map type
  google.protobuf.Timestamp created_at = 7;
  oneof contact {                  // OneOf: pilih salah satu
    string phone = 8;
    string telegram = 9;
  }
  Address address = 10;           // Nested message
}

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
  string postal_code = 4;
}

// Enum
enum UserRole {
  USER_ROLE_UNSPECIFIED = 0;  // Default value (harus 0)
  USER_ROLE_MEMBER = 1;
  USER_ROLE_ADMIN = 2;
  USER_ROLE_SUPERADMIN = 3;
}

// Request/Response messages
message GetUserRequest {
  int32 id = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
  string filter = 3;  // "role:admin AND name:john"
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
  int32 total_count = 3;
}

message CreateUserRequest {
  string name = 1;
  string email = 2;
  UserRole role = 3;
}

message UpdateUserRequest {
  User user = 1;
  google.protobuf.FieldMask update_mask = 2;  // Partial update
}

message DeleteUserRequest {
  int32 id = 1;
}

message Empty {}

Field Numbers & Wire Types

Text

# Wire Types dalam Protobuf:
#
# Type 0 (Varint)    : int32, int64, uint32, uint64, sint32, sint64, bool, enum
# Type 1 (64-bit)    : fixed64, sfixed64, double
# Type 2 (Length-del) : string, bytes, embedded messages, repeated fields
# Type 5 (32-bit)    : fixed32, sfixed32, float

# Field number encoding:
# Field 1-15  → 1 byte tag (hemat untuk field yang sering digunakan)
# Field 16-2047 → 2 byte tag
# → Prioritaskan field yang sering diakses ke nomor 1-15!

# DEFAULT VALUES (proto3):
# int32 → 0, string → "", bool → false
# enum → value pertama (harus 0), bytes → empty
# → Default value TIDAK di-serialize → menghemat bandwidth!

# Kompatibilitas backward/forward:
# ✅ Tambah field baru dengan nomor baru
# ✅ Hapus field (field lama tetap bisa di-parse, di-ignore)
# ❌ JANGAN ubah tipe field
# ❌ JANGAN ubah nomor field
# ❌ JANGAN reuse nomor field yang sudah dihapus

3. Service Definition

gRPC service didefinisikan di file .proto menggunakan keyword service. Setiap method di service akan di-generate menjadi interface di bahasa target.

Protobuf

// file: user_service.proto
syntax = "proto3";

package user;

import "user.proto";

// Service definition
service UserService {
  // Unary RPC — request-response biasa
  rpc GetUser(GetUserRequest) returns (User);

  // Unary dengan Empty
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);

  rpc CreateUser(CreateUserRequest) returns (User);

  rpc UpdateUser(UpdateUserRequest) returns (User);

  rpc DeleteUser(DeleteUserRequest) returns (Empty);

  // Server streaming — server mengirim banyak response
  rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent);

  // Client streaming — client mengirim banyak request
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);

  // Bidirectional streaming — keduanya stream
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message WatchUsersRequest {
  string filter = 1;
}

message UserEvent {
  enum EventType {
    EVENT_UNSPECIFIED = 0;
    EVENT_CREATED = 1;
    EVENT_UPDATED = 2;
    EVENT_DELETED = 3;
  }
  EventType type = 1;
  User user = 2;
}

message BatchCreateResponse {
  repeated User users = 1;
  int32 success_count = 2;
  int32 error_count = 3;
  repeated string errors = 4;
}

message ChatMessage {
  string user_id = 1;
  string message = 2;
  google.protobuf.Timestamp timestamp = 3;
}

Generate Code

Bash

# Install protoc compiler
# macOS
brew install protobuf

# Ubuntu
sudo apt install protobuf-compiler

# Generate Go code
protoc --go_out=. --go-grpc_out=. proto/*.proto

# Generate Python code
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. proto/*.proto

# Generate Node.js code
npx grpc_tools_node_protoc --js_out=import_style=commonjs:. \
  --grpc_out=grpc_js:. proto/*.proto

# Generate Java code
protoc --java_out=. --grpc-java_out=. proto/*.proto

# Generate ke banyak bahasa sekaligus
protoc \
  --go_out=. --go-grpc_out=. \
  --python_out=. --grpc_python_out=. \
  --js_out=import_style=commonjs:. --grpc_out=grpc_js:. \
  -I. proto/*.proto

Implementasi Server (Python)

Python

import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc
from google.protobuf import empty_pb2

class UserServiceServicer(user_pb2_grpc.UserServiceServicer):
    """Implementasi gRPC UserService"""

    def __init__(self):
        self.users = {}  # In-memory storage
        self.next_id = 1

    def GetUser(self, request, context):
        """Unary RPC — ambil user berdasarkan ID"""
        user = self.users.get(request.id)
        if not user:
            context.abort(grpc.StatusCode.NOT_FOUND,
                         f"User {request.id} not found")
        return user

    def CreateUser(self, request, context):
        """Buat user baru"""
        user = user_pb2.User(
            id=self.next_id,
            name=request.name,
            email=request.email,
            role=request.role
        )
        self.users[self.next_id] = user
        self.next_id += 1
        return user

    def ListUsers(self, request, context):
        """List users dengan pagination"""
        page_size = request.page_size or 10
        users_list = list(self.users.values())

        # Simple pagination
        start = int(request.page_token) if request.page_token else 0
        end = start + page_size
        page = users_list[start:end]

        return user_pb2.ListUsersResponse(
            users=page,
            next_page_token=str(end) if end < len(users_list) else "",
            total_count=len(users_list)
        )

    def WatchUsers(self, request, context):
        """Server streaming — kirim event saat user berubah"""
        import time
        # Simulasi: kirim semua user sebagai event CREATED
        for user in self.users.values():
            yield user_pb2.UserEvent(
                type=user_pb2.UserEvent.EVENT_CREATED,
                user=user
            )
            time.sleep(0.1)  # Delay antar event

def serve():
    server = grpc.server(
        futures.ThreadPoolExecutor(max_workers=10),
        options=[
            ('grpc.max_send_message_length', 50 * 1024 * 1024),
            ('grpc.max_receive_message_length', 50 * 1024 * 1024),
        ]
    )
    user_pb2_grpc.add_UserServiceServicer_to_server(
        UserServiceServicer(), server
    )
    server.add_insecure_port('[::]:50051')
    server.start()
    print("gRPC server running on port 50051")
    server.wait_for_termination()

if __name__ == '__main__':
    serve()

4. Streaming RPC

gRPC mendukung 4 mode komunikasi RPC:

ModeArahContoh Penggunaan
Unary1 request → 1 responseCRUD operations, query
Server Streaming1 request → N responseWatch/live feed, large file download
Client StreamingN request → 1 responseBatch upload, file upload
BidirectionalN request ↔ N responseChat, real-time collaboration

Bidirectional Streaming — Chat Implementation

Python

import grpc
from concurrent import futures
import threading
import queue
import user_pb2
import user_pb2_grpc

class ChatServicer(user_pb2_grpc.UserServiceServicer):
    def __init__(self):
        self.chat_queues = {}  # user_id → Queue
        self.lock = threading.Lock()

    def Chat(self, request_iterator, context):
        """Bidirectional streaming chat"""
        # Setup queue untuk client ini
        user_queue = queue.Queue()
        user_id = None

        # Thread untuk menerima message dari client
        def receive_messages():
            nonlocal user_id
            for message in request_iterator:
                user_id = message.user_id
                if user_id not in self.chat_queues:
                    with self.lock:
                        self.chat_queues[user_id] = user_queue

                # Broadcast ke semua user lain
                with self.lock:
                    for uid, q in self.chat_queues.items():
                        if uid != user_id:
                            q.put(message)

        # Start receiver thread
        receiver = threading.Thread(target=receive_messages)
        receiver.start()

        # Kirim message dari queue ke client
        try:
            while context.is_active():
                try:
                    message = user_queue.get(timeout=1.0)
                    yield message
                except queue.Empty:
                    continue
        finally:
            # Cleanup saat client disconnect
            if user_id and user_id in self.chat_queues:
                with self.lock:
                    del self.chat_queues[user_id]

# Client-side bidirectional streaming
def chat_client():
    channel = grpc.insecure_channel('localhost:50051')
    stub = user_pb2_grpc.UserServiceStub(channel)

    # Create message generator
    def send_messages():
        import sys
        for line in sys.stdin:
            yield user_pb2.ChatMessage(
                user_id="user-1",
                message=line.strip()
            )

    # Bidirectional streaming
    responses = stub.Chat(send_messages())
    for response in responses:
        print(f"[{response.user_id}]: {response.message}")

5. Interceptors & Metadata

Interceptors adalah middleware gRPC yang memungkinkan Anda menambahkan logika sebelum/sesudah setiap RPC call — mirip middleware di Express.js. Ini berguna untuk logging, authentication, rate limiting, dan tracing.

Python

import grpc
import time
import logging
from functools import wraps

logger = logging.getLogger(__name__)

# ===== SERVER INTERCEPTOR =====
class LoggingInterceptor(grpc.ServerInterceptor):
    """Log semua RPC calls"""

    def intercept_service(self, continuation, handler_call_details):
        method = handler_call_details.method
        start = time.time()

        # Extract metadata
        metadata = dict(handler_call_details.invocation_metadata)
        logger.info(f"→ {method} | metadata: {metadata}")

        # Continue ke handler
        handler = continuation(handler_call_details)

        if handler is None:
            return handler

        # Wrap handler untuk logging response
        if handler.unary_unary:
            return grpc.unary_unary_rpc_method_handler(
                self._wrap_unary(handler.unary_unary, method, start),
                request_deserializer=handler.request_deserializer,
                response_serializer=handler.response_serializer
            )
        return handler

    def _wrap_unary(self, behavior, method, start):
        def wrapper(request, context):
            try:
                response = behavior(request, context)
                elapsed = time.time() - start
                logger.info(f"← {method} OK ({elapsed:.3f}s)")
                return response
            except grpc.RpcError as e:
                elapsed = time.time() - start
                logger.error(f"✗ {method} ERROR: {e.code()} ({elapsed:.3f}s)")
                raise
        return wrapper

# ===== AUTH INTERCEPTOR =====
class AuthInterceptor(grpc.ServerInterceptor):
    """Validasi token di metadata"""

    VALID_TOKENS = {"secret-token-123": "admin", "token-456": "user"}

    def intercept_service(self, continuation, handler_call_details):
        metadata = dict(handler_call_details.invocation_metadata)

        # Skip auth untuk public methods
        if handler_call_details.method in ['/grpc.health.v1.Health/Check']:
            return continuation(handler_call_details)

        # Validasi token
        token = metadata.get('authorization', '')
        if token not in self.VALID_TOKENS:
            return grpc.unary_unary_rpc_method_handler(
                self._deny_access
            )

        return continuation(handler_call_details)

    def _deny_access(self, request, context):
        context.abort(grpc.StatusCode.UNAUTHENTICATED, "Invalid token")

# ===== CLIENT INTERCEPTOR =====
class ClientMetadataInterceptor(grpc.UnaryUnaryClientInterceptor):
    """Tambahkan metadata ke setiap client request"""

    def __init__(self, token, client_id):
        self.token = token
        self.client_id = client_id

    def intercept_unary_unary(self, continuation, client_call_details, request):
        # Tambahkan metadata
        metadata = list(client_call_details.invocation_metadata or [])
        metadata.append(('authorization', self.token))
        metadata.append(('x-client-id', self.client_id))
        metadata.append(('x-request-id', str(uuid.uuid4())))

        new_details = client_call_details._replace(
            invocation_metadata=metadata
        )
        return continuation(new_details, request)

# Setup server dengan interceptors
server = grpc.server(
    futures.ThreadPoolExecutor(max_workers=10),
    interceptors=[AuthInterceptor(), LoggingInterceptor()]
)

# Setup client dengan interceptor
channel = grpc.insecure_channel('localhost:50051')
interceptor = ClientMetadataInterceptor('secret-token-123', 'api-gateway')
channel = grpc.intercept_channel(channel, interceptor)
stub = user_pb2_grpc.UserServiceStub(channel)

6. Error Handling & Status Codes

gRPC menggunakan status codes yang terstandardisasi (berbeda dari HTTP status codes) untuk menandakan hasil operasi.

CodeNamaDeskripsi
0OKBerhasil
1CANCELLEDOperasi dibatalkan oleh client
2UNKNOWNError tidak diketahui
3INVALID_ARGUMENTParameter tidak valid
4DEADLINE_EXCEEDEDTimeout tercapai
5NOT_FOUNDResource tidak ditemukan
6ALREADY_EXISTSResource sudah ada
7PERMISSION_DENIEDTidak punya izin
8RESOURCE_EXHAUSTEDQuota/batas terlampaui
9FAILED_PRECONDITIONPrecondition tidak terpenuhi
10ABORTEDOperasi dibatalkan (conflict)
11OUT_OF_RANGEDi luar range yang valid
12UNIMPLEMENTEDMethod belum diimplementasi
13INTERNALError internal server
14UNAVAILABLEService tidak tersedia
15DATA_LOSSData loss (irreversible)
16UNAUTHENTICATEDAutentikasi gagal

7. Performa & Benchmark

Protobuf vs JSON Size Comparison

Text

# Data yang sama: User object dengan 10 fields

# JSON representation (147 bytes):
{"id":12345,"name":"John Doe","email":"john@example.com",
 "role":"ADMIN","tags":["dev","ops"],"created_at":"2026-01-15T10:30:00Z"}

# Protobuf representation (52 bytes):
# → 65% LEBIH KECIL dari JSON!

# Benchmark (100,000 iterations):
# ┌─────────────────────────┬────────────┬────────────┐
# │ Metric                  │ JSON       │ Protobuf   │
# ├─────────────────────────┼────────────┼────────────┤
# │ Serialize time          │ 45ms       │ 12ms       │
# │ Deserialize time        │ 52ms       │ 15ms       │
# │ Serialized size         │ 147 bytes  │ 52 bytes   │
# │ Throughput              │ 50K/s      │ 180K/s     │
# └─────────────────────────┴────────────┴────────────┘

# gRPC vs REST Benchmark:
# ┌─────────────────────────┬────────────┬────────────┐
# │ Scenario                │ REST       │ gRPC       │
# ├─────────────────────────┼────────────┼────────────┤
# │ Simple GET (1 record)   │ 2.3ms      │ 0.5ms      │
# │ List 1000 records       │ 45ms       │ 8ms        │
# │ Upload 1MB file         │ 120ms      │ 35ms       │
# │ 10K concurrent clients  │ 12K/s      │ 45K/s      │
# │ Streaming 1000 msg      │ N/A (REST) │ 15ms total │
# └─────────────────────────┴────────────┴────────────┘
📚 Ekosistem gRPC
  • gRPC-Web — Browser client via Envoy proxy
  • gRPC-Gateway — REST proxy otomatis dari .proto
  • grpcurl — CLI tool seperti curl untuk gRPC
  • BloomRPC / Postman — GUI untuk testing gRPC
  • Connect — gRPC-compatible framework dari Buf
  • Buf — Modern Protobuf toolchain

Quiz Pemahaman

Pertanyaan 1: Format serialisasi apa yang digunakan gRPC?

a) JSON
b) XML
c) Protocol Buffers
d) MessagePack

Pertanyaan 2: Berapa mode RPC yang didukung gRPC?

a) 2 (Unary dan Streaming)
b) 3
c) 4 (Unary, Server, Client, Bidirectional)
d) 1 (Hanya Unary)

Pertanyaan 3: Apa fungsi interceptors di gRPC?

a) Mengenkripsi data
b) Middleware untuk logging, auth, rate limiting
c) Kompresi payload
d) Mengatur routing

Pertanyaan 4: Berapa field number yang optimal untuk field yang sering diakses di Protobuf?

a) Di atas 100
b) 1-15 (1 byte tag)
c) Tidak ada bedanya
d) Selalu gunakan nomor ganjil

Pertanyaan 5: Apa keuntungan utama Protobuf dibanding JSON?

a) Lebih mudah dibaca manusia
b) Binary, lebih kecil dan lebih cepat
c) Tidak butuh schema
d) Didukung semua browser
 ad-slot-wide">
🔍 Zoom
100%
🎨 Tema