π API PROTOCOLS β Deep Dive: REST, gRPC, WebSocket, GraphQL
Pesan terpenting: Tidak ada protokol yang βterbaikβ. Yang ada adalah protokol yang paling sesuai dengan use case. Memilih gRPC hanya karena β5ms lebih cepat dari RESTβ tanpa memahami konteksnya = keputusan arsitektur yang salah.
Koreksi Angka TikTok
Perbandingan di viral tersebut bukan apple-to-apple:
- REST uncached 150ms vs GraphQL 15ms: GraphQL lebih lambat dari REST tanpa DataLoader karena N+1 problem
- WebSocket 50ms: itu connection setup overhead, bukan per-message latency. Setelah connected, pesan bisa < 1ms
- gRPC 5ms: valid hanya untuk internal service dengan persistent connection dan protobuf. Tidak cocok untuk browser langsung
Daftar Isi
- Arsitektur Setiap Protokol
- REST β Fondasi yang Sering Disalahpahami
- gRPC β Kecepatan Internal Service
- WebSocket β Real-Time Bidirectional
- GraphQL β Fleksibilitas Query
- Decision Matrix β Kapan Pakai Yang Mana
- Best Practices per Protokol
- Kombinasi Pattern β Dunia Nyata
Arsitektur Setiap Protokol
LAYER STACK per Protokol:
REST:
Client β HTTP/1.1 atau HTTP/2 β JSON payload β Server
[Stateless, request-response, human readable]
gRPC:
Client β HTTP/2 (multiplexed) β Protobuf binary β Server
[Bidirectional streaming, binary, strongly typed schema]
WebSocket:
Client β HTTP upgrade handshake β WS persistent connection β Server
[Stateful, bidirectional, low latency after connection]
GraphQL:
Client β HTTP/1.1 atau HTTP/2 β JSON query β GraphQL runtime β Resolver β DB
[Single endpoint, client-defined response shape, N+1 risk]
POSISI DI STACK:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β APPLICATION LAYER β
β REST GraphQL gRPC WebSocket β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β TRANSPORT LAYER β
β HTTP/1.1 HTTP/1.1 HTTP/2 HTTP β WS upgrade β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β NETWORK LAYER β
β TCP β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
REST β Fondasi yang Sering Disalahpahami
Apa yang Sebenarnya REST Itu
REST = Representational State Transfer
Bukan protokol β ini ARCHITECTURAL STYLE dengan 6 constraints:
1. Client-Server separation
2. Stateless (setiap request berdiri sendiri, server tidak simpan state client)
3. Cacheable (response bisa di-cache)
4. Uniform Interface (HTTP verbs: GET/POST/PUT/PATCH/DELETE)
5. Layered System (boleh ada proxy/cache di antara client-server)
6. Code on Demand (opsional: server bisa kirim executable code)
Yang sering dikira REST tapi bukan:
β "REST hanya JSON" β XML, YAML, plain text semua valid
β "REST = CRUD biasa" β REST bisa kompleks
β "REST pasti lambat" β dengan caching yang benar, REST bisa sangat cepat
REST yang BENAR vs REST yang Buruk:
BURUK: POST /getUser (verb di URL, pakai POST untuk read)
POST /deleteUser (aksi di URL)
GET /users?action=delete (berbahaya!)
BENAR: GET /users/{id} (read)
POST /users (create)
PUT /users/{id} (replace full)
PATCH /users/{id} (update partial)
DELETE /users/{id} (delete)
Caching β Kenapa REST Bisa 25ms
HTTP CACHING HIERARCHY:
Request β [Browser Cache] β [CDN Cache] β [Reverse Proxy] β [App Cache] β DB
β β β β
Cache-Control Edge cache Nginx/Varnish Redis/Memcached
Cache-Control Headers (kunci):
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Cache-Control: public, max-age=300 β
β β Siapapun (CDN, browser) boleh cache selama 5 menit β
β β
β Cache-Control: private, max-age=60 β
β β Hanya browser yang boleh cache (sensitif per user) β
β β
β Cache-Control: no-cache β
β β Selalu cek ke server, tapi boleh pakai cache jika valid β
β β
β Cache-Control: no-store β
β β JANGAN cache sama sekali (sensitif: banking, health) β
β β
β ETag: "abc123" β
β β Fingerprint response. Client kirim If-None-Match β
β β Server return 304 Not Modified jika tidak berubah β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
KENAPA gRPC tidak bisa seredah 25ms dengan caching:
β Protobuf bukan human-readable β CDN tidak bisa cache dengan mudah
β HTTP/2 streaming = tidak cocok untuk standard CDN caching
β REST + CDN caching = lebih scalable untuk public API
REST Best Practices
# FastAPI β contoh REST yang proper
from fastapi import FastAPI, HTTPException, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import httpx
app = FastAPI()
# βββ RESPONSE YANG KONSISTEN ββββββββββββββββββββββββββββββββββ
# Selalu gunakan response schema yang sama
class APIResponse(BaseModel):
success: bool
data: dict | list | None = None
error: str | None = None
pagination: dict | None = None
# βββ HTTP STATUS CODE YANG BENAR ββββββββββββββββββββββββββββββ
# Ini yang sering salah: semua return 200 padahal ada error
@app.get("/users/{user_id}")
async def get_user(user_id: int):
user = await db.get_user(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, # β bukan 200!
detail="User not found"
)
return JSONResponse(
status_code=status.HTTP_200_OK,
content={"success": True, "data": user.dict()}
)
@app.post("/users", status_code=status.HTTP_201_CREATED) # β 201 untuk create
async def create_user(user: UserCreate):
...
# βββ VERSIONING βββββββββββββββββββββββββββββββββββββββββββββββ
# Selalu version API untuk backward compatibility
# /api/v1/users β versi lama (tetap jalan)
# /api/v2/users β versi baru dengan breaking changes
# βββ PAGINATION YANG PROPER βββββββββββββββββββββββββββββββββββ
@app.get("/users")
async def list_users(
page: int = 1,
limit: int = 20, # β default limit, jangan no limit!
cursor: str | None = None # cursor-based untuk large dataset
):
# Cursor-based pagination lebih efisien untuk large table
# dibanding offset-based (LIMIT x OFFSET y semakin lambat semakin besar offset)
...
return {
"data": users,
"pagination": {
"next_cursor": "abc123",
"has_more": True,
"total": 10000
}
}
# βββ IDEMPOTENCY KEY ββββββββββββββββββββββββββββββββββββββββββ
# Untuk POST yang tidak boleh duplicate (payment, order)
@app.post("/payments")
async def create_payment(
payment: PaymentCreate,
idempotency_key: str = Header(...) # Client generate UUID, kirim di header
):
# Cek apakah key ini sudah pernah diproses
existing = await cache.get(f"payment:{idempotency_key}")
if existing:
return existing # Return hasil sebelumnya, bukan proses ulang
result = await process_payment(payment)
await cache.set(f"payment:{idempotency_key}", result, ttl=86400)
return resultgRPC β Kecepatan Internal Service
Mengapa 5ms β Mekanisme di Baliknya
KENAPA gRPC LEBIH CEPAT DARI REST (dalam kondisi yang tepat):
1. PROTOCOL BUFFERS (Protobuf) vs JSON:
JSON:
{"user_id": 12345, "username": "azhar", "email": "azhar@example.com"}
β 64 bytes, human readable, perlu parsing string
Protobuf:
\x08\xb9\x60\x12\x05azhar\x1a\x13azhar@example.com
β ~25 bytes (60% lebih kecil), binary, no parsing overhead
BENCHMARK SERIALIZATION:
JSON serialize: ~1.5ΞΌs per operation
Protobuf serialize: ~0.3ΞΌs per operation (5x lebih cepat)
2. HTTP/2 MULTIPLEXING:
HTTP/1.1: satu request per connection (kecuali pakai keep-alive)
Request 1 βββββββββββββββΊ Response 1
Request 2 βββββββββββββββΊ Response 2 (menunggu Response 1)
HTTP/2 gRPC: multiple stream dalam satu connection
Stream 1: Request βββββββββββββββΊ Response 1
Stream 2: Request βββββββββββββββΊ Response 2
Stream 3: Request βββββββββββββββΊ Response 3
(semua parallel, tidak blocking)
3. PERSISTENT CONNECTION:
REST: setiap request bisa buka connection baru (TCP handshake overhead)
gRPC: connection tetap terbuka, reuse untuk banyak call
β TCP handshake dihilangkan setelah koneksi pertama
4. STRONGLY TYPED CONTRACT (.proto file):
β Compiler yang cek type mismatch, bukan runtime error
β Auto-generate client code untuk berbagai bahasa
.proto File β Schema First
// user.proto
syntax = "proto3";
package user;
// Definisi message = tipe data
message User {
int32 id = 1;
string username = 2;
string email = 3;
repeated string roles = 4; // array of string
optional string bio = 5; // nullable field
}
message GetUserRequest {
int32 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 limit = 2;
}
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
bool has_more = 3;
}
// Definisi service = endpoint
service UserService {
// Unary: satu request, satu response (seperti REST)
rpc GetUser(GetUserRequest) returns (User);
// Server streaming: satu request, BANYAK response
rpc ListUsers(ListUsersRequest) returns (stream User);
// Client streaming: BANYAK request, satu response
rpc BatchCreateUsers(stream User) returns (BatchResult);
// Bidirectional streaming: banyak request + banyak response
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}# Server implementation (Python)
import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc
class UserServicer(user_pb2_grpc.UserServiceServicer):
# Unary RPC
def GetUser(self, request, context):
user = db.get_user(request.id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details("User not found")
return user_pb2.User()
return user_pb2.User(
id=user.id,
username=user.username,
email=user.email
)
# Server streaming RPC
def ListUsers(self, request, context):
# Yield satu per satu β client terima stream
for user in db.list_users(page=request.page, limit=request.limit):
yield user_pb2.User(
id=user.id,
username=user.username
)
# Jika client cancel (disconnect), context.is_active() = False
if not context.is_active():
break
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserServicer(), server)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()Kapan gRPC Tidak Cocok
JANGAN pakai gRPC untuk:
β Public API yang diakses browser langsung
β Browser tidak support HTTP/2 trailer (dibutuhkan gRPC)
β Solusi: gRPC-Web (tapi butuh proxy seperti Envoy)
β Lebih baik: REST atau GraphQL untuk browser
β Human debugging tanpa tools
β Protobuf binary = tidak bisa curl dan lihat response
β Butuh: grpcurl, Postman gRPC, atau decode manual
β Simple CRUD dengan sedikit client
β Overhead setup .proto + code generation tidak worth it
β REST + JSON jauh lebih simple
COCOK untuk:
β
Internal microservice ke microservice
β
High-throughput data pipeline
β
Mobile app (bandwidth efficiency)
β
Polyglot environment (auto-generate client untuk 10+ bahasa)
β
Real-time bidirectional streaming (bukan chat β untuk data stream)
WebSocket β Real-Time Bidirectional
Connection Lifecycle yang Perlu Dipahami
WEBSOCKET CONNECTION FLOW:
Fase 1: HTTP Upgrade Handshake (hanya sekali, ~50ms overhead)
Client:
GET /ws HTTP/1.1
Host: server.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Server:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Fase 2: Persistent Connection (setelah ini latency per pesan ~1-5ms)
Client ββββ message βββββΊ Server
Server ββββ message βββββΊ Client
Client ββββ message βββββΊ Server
(bidirectional, kapanpun, tanpa perlu request-response pattern)
Fase 3: Close Handshake
Client ββββ CLOSE frame βββββΊ Server
Server ββββ CLOSE frame βββββΊ Client
OVERHEAD YANG SERING SALAH KAPRAH:
"WebSocket 50ms" di TikTok = biaya FASE 1 saja
Setelah handshake: setiap pesan hanya 2-byte header overhead
vs REST: HTTP header 500-800 bytes per request
Implementasi WebSocket yang Proper
# FastAPI WebSocket β dengan proper error handling dan rooms
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import Dict, List
import json
import asyncio
app = FastAPI()
class ConnectionManager:
"""Manage multiple WebSocket connections dengan rooms"""
def __init__(self):
# room_id β list of WebSocket connections
self.rooms: Dict[str, List[WebSocket]] = {}
# websocket β metadata (user_id, room_id)
self.metadata: Dict[WebSocket, dict] = {}
async def connect(self, websocket: WebSocket, room_id: str, user_id: str):
await websocket.accept()
if room_id not in self.rooms:
self.rooms[room_id] = []
self.rooms[room_id].append(websocket)
self.metadata[websocket] = {"user_id": user_id, "room_id": room_id}
# Notify others in room
await self.broadcast_to_room(
room_id,
{"type": "user_joined", "user_id": user_id},
exclude=websocket
)
def disconnect(self, websocket: WebSocket):
meta = self.metadata.get(websocket, {})
room_id = meta.get("room_id")
if room_id and room_id in self.rooms:
self.rooms[room_id].remove(websocket)
if not self.rooms[room_id]:
del self.rooms[room_id]
del self.metadata[websocket]
return meta
async def broadcast_to_room(self, room_id: str, message: dict, exclude=None):
if room_id not in self.rooms:
return
# Kirim ke semua connection dalam room, kecuali yang di-exclude
dead_connections = []
for websocket in self.rooms[room_id]:
if websocket == exclude:
continue
try:
await websocket.send_json(message)
except Exception:
dead_connections.append(websocket)
# Cleanup dead connections
for dead in dead_connections:
self.disconnect(dead)
manager = ConnectionManager()
@app.websocket("/ws/{room_id}")
async def websocket_endpoint(
websocket: WebSocket,
room_id: str,
token: str # Auth via query param untuk WS
):
# Validate token sebelum accept
user = await validate_token(token)
if not user:
await websocket.close(code=4001, reason="Unauthorized")
return
await manager.connect(websocket, room_id, user.id)
try:
while True:
# Terima pesan dari client
data = await websocket.receive_json()
# Validate message schema
if "type" not in data or "content" not in data:
await websocket.send_json({
"type": "error",
"message": "Invalid message format"
})
continue
# Handle berbagai tipe message
if data["type"] == "chat":
await manager.broadcast_to_room(
room_id,
{
"type": "chat",
"user_id": user.id,
"content": data["content"],
"timestamp": datetime.utcnow().isoformat()
}
)
elif data["type"] == "ping":
await websocket.send_json({"type": "pong"})
except WebSocketDisconnect:
meta = manager.disconnect(websocket)
await manager.broadcast_to_room(
meta.get("room_id", ""),
{"type": "user_left", "user_id": user.id}
)Masalah WebSocket yang Sering Diabaikan
PROBLEM 1: Scalability dengan Multiple Server Instance
Jika punya 3 server:
Server A ββββ user1 (ws connected)
Server B ββββ user2 (ws connected)
Server C ββββ user3 (ws connected)
user1 kirim pesan ke user2:
β Server A tidak tahu user2 ada di Server B!
β Pesan tidak terdeliver
SOLUSI: Pub/Sub dengan Redis
Server A publish event ke Redis channel
Server B subscribe ke channel yang sama
β Broadcast ke semua server
import redis.asyncio as aioredis
redis = await aioredis.from_url("redis://localhost")
# Publish (sender server)
await redis.publish("chat:room123", json.dumps(message))
# Subscribe (semua server)
pubsub = redis.pubsub()
await pubsub.subscribe("chat:room123")
async for message in pubsub.listen():
# Forward ke local WebSocket connections
await local_broadcast(message)
PROBLEM 2: Reconnection Logic (wajib di client)
Client JavaScript yang proper:
class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.reconnectDelay = 1000;
this.maxDelay = 30000;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.reconnectDelay = 1000; // reset delay
console.log("Connected");
};
this.ws.onclose = (event) => {
if (event.code !== 1000) { // 1000 = normal close
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(
this.reconnectDelay * 2,
this.maxDelay
); // exponential backoff
}
};
// Heartbeat untuk detect dead connection
setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({type: "ping"}));
}
}, 30000);
}
}
PROBLEM 3: Memory Leak dari Stale Connections
β Mobile browser background = connection "zombie" (tidak close, tidak active)
β Server menyimpan connection object yang tidak bisa dihubungi
β Solusi: Heartbeat + server-side timeout
GraphQL β Fleksibilitas Query
N+1 Problem β Mengapa GraphQL Bisa LEBIH Lambat
SCHEMA:
type User {
id: ID
posts: [Post] β relasi
}
QUERY:
{
users { # Ambil 100 user
id
posts { # Untuk SETIAP user, ambil posts
title
}
}
}
TANPA DATALOADER β N+1 Queries ke Database:
Query 1: SELECT * FROM users β 100 user
Query 2: SELECT * FROM posts WHERE user_id = 1
Query 3: SELECT * FROM posts WHERE user_id = 2
Query 4: SELECT * FROM posts WHERE user_id = 3
...
Query 101: SELECT * FROM posts WHERE user_id = 100
TOTAL: 101 queries untuk 100 user!
Ini yang bikin GraphQL bisa 10x LEBIH LAMBAT dari REST
DENGAN DATALOADER β Batched:
Query 1: SELECT * FROM users
Query 2: SELECT * FROM posts WHERE user_id IN (1,2,3,...,100)
TOTAL: 2 queries saja!
Implementasi DataLoader (Python, Strawberry):
import strawberry
from strawberry.dataloader import DataLoader
from typing import List
async def load_posts_for_users(user_ids: List[int]) -> List[List[Post]]:
# Satu query untuk SEMUA user sekaligus
all_posts = await db.query(
"SELECT * FROM posts WHERE user_id = ANY($1)",
user_ids
)
# Group by user_id
posts_by_user = defaultdict(list)
for post in all_posts:
posts_by_user[post.user_id].append(post)
return [posts_by_user[uid] for uid in user_ids]
posts_loader = DataLoader(load_fn=load_posts_for_users)
@strawberry.type
class User:
id: int
@strawberry.field
async def posts(self) -> List[Post]:
return await posts_loader.load(self.id)
# DataLoader akan batch semua .load() yang terjadi dalam satu request
Query Complexity β Mencegah Abuse
MASALAH:
GraphQL memperbolehkan client query apapun yang mereka mau
β Nested query yang dalam bisa jadi "DoS attack"
{
users {
friends {
friends {
friends {
friends { # 4 level dalam = exponential DB query
id
}
}
}
}
}
}
SOLUSI: Query Cost Analysis
from graphql import GraphQLSchema
from graphql_query_complexity import QueriesComplexityValidator, SimpleEstimator
MAX_COMPLEXITY = 100 # Tentukan batas
def validate_complexity(schema, query):
complexity = get_query_complexity(
schema,
query,
estimators=[
SimpleEstimator(default_complexity=1),
# Relasi lebih mahal
FieldEstimator(field="users.posts", complexity=5),
]
)
if complexity > MAX_COMPLEXITY:
raise Exception(f"Query complexity {complexity} exceeds maximum {MAX_COMPLEXITY}")
# Tambah ke middleware GraphQL
Kapan GraphQL Worth It
WORTH IT jika:
β
Banyak client dengan kebutuhan data berbeda
β Mobile: butuh field A, B, C
β Web: butuh field A, B, C, D, E
β Dengan REST: buat 2 endpoint atau overfetch
β Dengan GraphQL: satu endpoint, client pilih field
β
Rapid frontend iteration
β Frontend bisa tambah field tanpa backend deployment
β Self-documenting via introspection
β
Aggregasi dari multiple data source
β GraphQL stitching / federation
TIDAK WORTH IT jika:
β Simple CRUD API dengan sedikit client
β Public API (throttling lebih kompleks, persisted queries wajib)
β Tim backend kecil (overhead schema design + N+1 + complexity)
β High-performance real-time (WebSocket atau gRPC lebih cocok)
Decision Matrix β Kapan Pakai Yang Mana
DIAGRAM KEPUTUSAN:
Real-time?
/ \
YA TIDAK
β β
Bidirectional? Many clients/
β different needs?
/ \ / \
YA TIDAK YA TIDAK
β β β β
WebSocket SSE/ GraphQL Internal
Polling service?
/ \
YA TIDAK
β β
gRPC REST
| Kriteria | REST | gRPC | WebSocket | GraphQL |
|---|---|---|---|---|
| Browser support langsung | β Native | β Butuh proxy | β Native | β Native |
| Caching built-in | β HTTP cache | β Terbatas | β Tidak ada | β οΈ Hanya query |
| Real-time | β Polling only | β οΈ Streaming | β Native | β οΈ Subscription |
| Binary efficiency | β JSON | β Protobuf | β οΈ Frame overhead | β JSON |
| Type safety | β οΈ Manual | β .proto contract | β Manual | β Schema |
| Debugging | β curl/Postman | β Butuh grpcurl | β οΈ Butuh tool | β Playground |
| Learning curve | β Rendah | π΄ Tinggi | π‘ Medium | π‘ Medium |
| Scaling | β Stateless | β | β Stateful | β |
| CDN friendly | β | β | β | β οΈ |
Use Case konkret
E-COMMERCE:
βββ Product catalog API: REST (CDN cache, SEO friendly)
βββ Search API: REST atau GraphQL (flexible query)
βββ Inventory serviceβservice: gRPC
βββ Order notification ke client: WebSocket
βββ Checkout: REST (idempotency key wajib)
CHAT APP:
βββ Auth, user profile: REST
βββ Message history: REST (cacheable)
βββ Real-time messaging: WebSocket
GAME MULTIPLAYER:
βββ Auth, leaderboard: REST
βββ Game state sync: WebSocket (atau UDP via QUIC)
βββ Matchmaking service: gRPC internal
FINTECH DASHBOARD:
βββ Account data: REST
βββ Live price feed: WebSocket (SSE alternatif)
βββ Transaction service: REST + idempotency
βββ Internal analytics pipeline: gRPC streaming
Best Practices per Protokol
REST Best Practices Checklist
API DESIGN:
β Gunakan HTTP status code yang benar (tidak semua 200)
β URL menggunakan noun bukan verb (/users bukan /getUsers)
β Versi API di URL (/v1/, /v2/)
β Response schema konsisten
β Error response menyertakan error_code yang bisa diprogram
PERFORMANCE:
β Implementasi caching dengan Cache-Control header
β Gunakan ETag untuk conditional requests
β Pagination dengan cursor (bukan offset untuk large data)
β Compression: gzip/br untuk response > 1KB
β Connection pooling untuk DB
SECURITY:
β Rate limiting per IP dan per user
β Authentication di setiap endpoint (kecuali yang public)
β Validate semua input (Pydantic, Joi, Zod)
β CORS yang strict (bukan *)
β HTTPS only, HSTS header
β Idempotency key untuk non-idempotent POST
gRPC Best Practices Checklist
PROTO DESIGN:
β Gunakan snake_case untuk field names
β Selalu mulai dari field number 1 (jangan skip)
β Tandai field deprecated dengan [deprecated = true]
β Gunakan Any atau oneof untuk polymorphic field
PERFORMANCE:
β Connection pool: reuse channel, jangan buat baru per request
β Deadline/timeout di setiap RPC call
β Implement retry dengan exponential backoff
OBSERVABILITY:
β gRPC status code di setiap response
β Interceptor untuk logging dan metrics
β Distributed tracing (OpenTelemetry)
SECURITY:
β TLS mutual authentication (mTLS) untuk internal service
β Auth via metadata (bukan query param)
WebSocket Best Practices Checklist
CONNECTION:
β Authenticate SEBELUM upgrade (atau di pesan pertama setelah connect)
β Implement heartbeat/ping-pong (setiap 30 detik)
β Detect stale connection dan close dari server side
β Client implement reconnection dengan exponential backoff
MESSAGE:
β Define message type/schema yang jelas (JSON dengan type field)
β Message size limit (cegah memory exhaustion)
β Sequence number untuk detect missed message
SCALING:
β Pub/Sub (Redis) untuk cross-server broadcast
β Sticky session atau distributed session storage
β Graceful shutdown: kirim CLOSE frame ke semua client
SECURITY:
β Validate origin header (CSRF via WebSocket)
β Rate limit per connection
β Encrypt payload jika data sensitif (walaupun sudah WSS)
GraphQL Best Practices Checklist
SCHEMA:
β DataLoader untuk semua relasi (WAJIB)
β Query complexity limit
β Query depth limit
β Pagination untuk semua list type
PERFORMANCE:
β Persisted queries untuk production (hash-based, cegah arbitrary query)
β Response caching (per-query hash)
β APQ (Automatic Persisted Queries) untuk bandwidth saving
SECURITY:
β Disable introspection di production
β Field-level authorization (bukan hanya resolver level)
β Input validation + sanitization
β Rate limit berdasarkan query complexity, bukan jumlah request
Kombinasi Pattern β Dunia Nyata
POLA UMUM YANG BENAR:
BACKEND FOR FRONTEND (BFF):
ββββββββββββ WebSocket βββββββ
β Browser βββββββββββββββββΊβ BFF ββββ gRPC βββΊ Microservices
ββββββββββββ REST/GraphQL βββββββ
BFF menghadap client dengan REST atau GraphQL
BFF berkomunikasi ke internal services via gRPC
Real-time updates ke client via WebSocket
EVENT-DRIVEN + REST:
REST β synchronous read (GET /orders/123)
gRPC β internal service call (inventory.CheckStock)
Events β Kafka/RabbitMQ untuk async processing
WebSocket β notify client bahwa order sudah ready
HYBRID CACHING:
βββββ CDN (geografis) ββββββββββββββββββ
Client βββββββββββΊ Load Balancer
βββββ App Server βββΊ Redis βββΊ PostgreSQL
β
Stale-While-Revalidate:
Return cached (stale) data
sambil fetch fresh di background
Prioritas Belajar
Urutan yang paling worth untuk web developer/DevOps:
- REST yang benar β 80% API di dunia ini REST. Banyak yang βREST-ishβ tapi salah kaprah
- WebSocket β real-time adalah fitur yang semakin banyak diminta
- GraphQL β hanya jika ada multiple client dengan kebutuhan berbeda
- gRPC β saat mulai bangun microservice internal dengan traffic tinggi
Jangan skip REST ke gRPC hanya karena β5ms lebih cepatβ. REST yang di-cache lebih cepat dari gRPC uncached.
π Lihat Juga
- System Design β arsitektur yang menggunakan protokol ini
- Cloud Infrastructure β deployment dan service mesh (Istio untuk gRPC)
- Security Tools β WAF untuk REST/GraphQL protection
- LLM Security β API security testing sebagai attack surface
- CD β API testing dalam pipeline
API Protocols Deep Dive | REST (Caching, Idempotency) Β· gRPC (Protobuf, Streaming) Β· WebSocket (Reconnection, Scaling) Β· GraphQL (N+1, DataLoader, Complexity) Β· Decision Matrix