Bagaimana Client dan Server Berkomunikasi di Sistem Modern?
Setiap sistem yang melibatkan lebih dari satu komponen — aplikasi mobile yang berkomunikasi dengan backend, microservice yang saling bertukar data, atau sistem yang terintegrasi dengan pihak ketiga — menghadapi satu pertanyaan fundamental yang jawabannya jauh dari trivial: bagaimana cara terbaik mereka berkomunikasi? Pilihan antara REST, GraphQL, gRPC, WebSocket, SSE, Webhook, Message Queue, dan tRPC bukan sekadar preferensi teknikal — ia menentukan latensi, skalabilitas, kompleksitas operasional, dan pengalaman developer yang akan dirasakan tim selama bertahun-tahun. Artikel ini membahas setiap pola secara mendalam: dari mana ia berasal, bagaimana ia bekerja di level protokol, apa kekuatan dan batasannya, dan kapan ia adalah pilihan yang tepat.
Peta Besar: Dimensi yang Membedakan
Sebelum masuk ke masing-masing teknologi, ada baiknya memahami dimensi-dimensi yang membedakan satu pola komunikasi dari yang lain. Ini akan membantu mengevaluasi setiap pilihan dengan kerangka yang konsisten.
flowchart TD
Q1{Komunikasi\nsynchronous\natau async?} -- Synchronous --> Q2{Siapa yang\ninisiasi komunikasi?}
Q1 -- Asynchronous --> Q3{Butuh ordering\ndan durability?}
Q2 -- Client --> Q4{Butuh kontrol\npenuh atas\nstruktur data?}
Q2 -- Server push --> Q5{Dua arah\natau satu arah?}
Q4 -- Ya --> GQL[GraphQL]
Q4 -- Tidak --> Q6{Internal service\natau public API?}
Q6 -- Internal --> GRPC[gRPC]
Q6 -- Public --> REST[REST]
Q5 -- Dua arah --> WS[WebSocket]
Q5 -- Satu arah --> SSE[Server-Sent Events]
Q3 -- Ya, butuh durability --> MQ[Message Queue\n/ Event Streaming]
Q3 -- Tidak perlu --> WH[Webhook]
style GQL fill:#e3f2fd,stroke:#1e88e5
style GRPC fill:#e8f5e9,stroke:#43a047
style REST fill:#f3e5f5,stroke:#8e24aa
style WS fill:#fff3e0,stroke:#fb8c00
style SSE fill:#fce4ec,stroke:#e91e63
style MQ fill:#e0f2f1,stroke:#00897b
style WH fill:#fff8e1,stroke:#fdd835Diagram ini adalah titik awal — bukan keputusan final. Setiap teknologi punya nuansa yang tidak bisa ditangkap oleh satu decision tree. Bagian-bagian berikut membahas nuansa tersebut.
REST
REST (Representational State Transfer) adalah pola komunikasi yang paling luas digunakan di dunia web. Hampir setiap engineer pernah membangun atau mengkonsumsi REST API, tapi tidak semua memahami mengapa ia dirancang seperti itu dan apa konsekuensi dari prinsip-prinsipnya.
Asal-usul dan Prinsip Desain
Roy Fielding memperkenalkan REST dalam disertasi doktornya di tahun 2000 sebagai kritik terhadap SOAP dan sistem web yang terlalu kompleks pada era itu. REST bukan protokol — ia adalah architectural style, sekumpulan constraint yang jika diikuti menghasilkan sistem yang scalable, stateless, dan mudah di-cache.
Enam constraint utama REST yang sering diabaikan dalam implementasi nyata:
- Stateless: setiap request harus mengandung semua informasi yang dibutuhkan server. Server tidak menyimpan state dari request sebelumnya.
- Client-server separation: client tidak perlu tahu cara server menyimpan data; server tidak perlu tahu cara client merender UI.
- Cacheable: response harus mendefinisikan apakah ia bisa di-cache, untuk berapa lama, dan oleh siapa.
- Uniform interface: resource diidentifikasi lewat URL, dimanipulasi lewat representasi (JSON/XML), pesan self-descriptive, dan HATEOAS.
- Layered system: client tidak perlu tahu apakah ia terhubung langsung ke server atau melalui load balancer, CDN, atau proxy.
- Code on demand (opsional): server bisa mengirim kode yang dieksekusi client (seperti JavaScript).
Cara Kerja
sequenceDiagram
participant C as Client
participant LB as Load Balancer
participant API as API Server
participant DB as Database
C->>LB: HTTP GET /users/42
LB->>API: Forward request
API->>DB: SELECT * FROM users WHERE id=42
DB-->>API: Row data
API-->>LB: 200 OK {"id":42,"name":"Alice",...}
LB-->>C: 200 OK {"id":42,"name":"Alice",...}
Note over C,API: Stateless — setiap request independen
Note over API: Server tidak ingat request sebelumnyaContoh Implementasi (Go)
// REST API handler yang mengikuti prinsip-prinsip HTTP dengan benar
package main
import (
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// BENAR: gunakan HTTP method dan status code yang semantically tepat
func getUserHandler(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
// 400 Bad Request — input client tidak valid
http.Error(w, `{"error":"invalid user id"}`, http.StatusBadRequest)
return
}
user, err := getUserByID(id)
if err != nil {
// 404 Not Found — resource tidak ada
http.Error(w, `{"error":"user not found"}`, http.StatusNotFound)
return
}
// Set Cache-Control header — sering dilupakan tapi penting
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "public, max-age=60")
json.NewEncoder(w).Encode(user)
}
// ANTI-PATTERN: semua request ke satu endpoint dengan action di body
// POST /api {"action": "getUser", "id": 42}
// ✗ Ini bukan REST, ini adalah RPC yang menyamar sebagai REST
// ✗ Tidak bisa di-cache, tidak bisa pakai HTTP method semantics
Kapan REST Tepat dan Tidak Tepat
GUNAKAN REST jika:
✓ Public API yang dikonsumsi developer eksternal
✓ CRUD operations yang straightforward
✓ Tim dengan tooling dan background yang beragam
✓ Butuh cacheability di level HTTP dan CDN
✓ Resource model bisnis sudah jelas dan stabil
PERTIMBANGKAN ALTERNATIF jika:
✗ Frontend butuh subset data yang berbeda-beda (→ GraphQL)
✗ Komunikasi antar service internal dengan throughput tinggi (→ gRPC)
✗ Butuh push notifikasi real-time dari server (→ WebSocket / SSE)
✗ Over-fetching dan under-fetching jadi masalah nyata di mobile (→ GraphQL)
GraphQL
GraphQL hadir untuk menyelesaikan masalah spesifik yang muncul ketika REST digunakan untuk aplikasi dengan kebutuhan data yang sangat bervariasi — terutama aplikasi mobile dan frontend dengan banyak view yang berbeda.
Asal-usul dan Motivasi
Facebook mengembangkan GraphQL secara internal sejak 2012 untuk mengatasi masalah yang sangat konkret: aplikasi mobile mereka membutuhkan data yang berbeda untuk setiap view, sementara REST API yang ada selalu mengembalikan terlalu banyak atau terlalu sedikit data. Solusinya adalah memberikan kendali penuh kepada client untuk mendefinisikan struktur data yang dibutuhkan.
Cara Kerja
sequenceDiagram
participant C as Client (React / Mobile)
participant GW as GraphQL Server
participant US as User Service
participant OS as Order Service
C->>GW: POST /graphql\n{ user(id:42) { name orders { id total } } }
GW->>US: Resolve user field
US-->>GW: {id:42, name:"Alice"}
GW->>OS: Resolve orders field untuk user 42
OS-->>GW: [{id:1,total:150},{id:2,total:75}]
GW-->>C: { user: { name:"Alice", orders:[...] } }
Note over C,GW: Client hanya menerima field yang diminta
Note over GW: Satu request bisa resolve data dari banyak serviceTiga Konsep Utama
Query digunakan untuk membaca data. Client mendefinisikan persis field apa yang dibutuhkan — tidak lebih, tidak kurang.
Mutation digunakan untuk mengubah data. Berbeda dari REST yang menggunakan HTTP method berbeda, GraphQL menggunakan kata kunci mutation sebagai konvensinya.
Subscription digunakan untuk data real-time. Server akan push update ke client setiap kali data yang di-subscribe berubah — biasanya menggunakan WebSocket di bawahnya.
# Query — ambil hanya field yang dibutuhkan view ini
query GetUserProfile($id: ID!) {
user(id: $id) {
name
email
# Tidak perlu field lain yang tidak ditampilkan di view ini
recentOrders(limit: 3) {
id
total
status
}
}
}
# Mutation — ubah data
mutation UpdateEmail($userId: ID!, $newEmail: String!) {
updateUserEmail(userId: $userId, email: $newEmail) {
id
email
updatedAt
}
}
# Subscription — terima update real-time
subscription OnOrderStatusChange($orderId: ID!) {
orderStatusChanged(orderId: $orderId) {
id
status
updatedAt
}
}
GraphQL membuat N+1 query problem lebih mudah terjadi karena client bisa meminta data relasional yang dalam secara bebas. Gunakan DataLoader pattern untuk batching request ke database, dan terapkan query depth limiting untuk mencegah client membuat query yang terlalu dalam dan mahal.
gRPC
gRPC adalah framework RPC (Remote Procedure Call) yang dibangun Google di atas HTTP/2 dan Protocol Buffers. Ia dirancang untuk komunikasi internal antar service yang membutuhkan performa tinggi dan type safety yang ketat.
Asal-usul dan Motivasi
Google membutuhkan cara yang efisien untuk menghubungkan ratusan microservice internal mereka. Solusi yang mereka kembangkan — Stubby — akhirnya dirilis sebagai gRPC pada 2016. Di baliknya ada dua komponen kunci: HTTP/2 sebagai transport layer, dan Protocol Buffers sebagai format serialisasi.
Protocol Buffers (Protobuf) adalah format biner yang jauh lebih compact dari JSON, dan karena strongly typed, ia menghasilkan client dan server code secara otomatis dari satu schema definisi (.proto file).
Cara Kerja
sequenceDiagram
participant SA as Service A (Go)
participant SB as Service B (Python)
Note over SA,SB: Keduanya di-generate dari .proto yang sama
SA->>SB: Binary Protobuf over HTTP/2\nCreateOrder(userId=42, items=[...])
Note over SB: Deserialize Protobuf\nJalankan logika bisnis
SB-->>SA: Binary Protobuf\nOrderResponse(orderId=999, status=CREATED)
Note over SA: Deserialize Protobuf\nLanjutkan prosesEmpat Pola Komunikasi gRPC
gRPC mendukung empat pola yang tidak dimiliki REST secara native:
flowchart LR
subgraph Unary["1. Unary RPC"]
C1[Client] -->|satu request| S1[Server]
S1 -->|satu response| C1
end
subgraph ServerStream["2. Server Streaming"]
C2[Client] -->|satu request| S2[Server]
S2 -->|stream responses| C2
end
subgraph ClientStream["3. Client Streaming"]
C3[Client] -->|stream requests| S3[Server]
S3 -->|satu response| C3
end
subgraph BiStream["4. Bidirectional Streaming"]
C4[Client] <-->|stream dua arah| S4[Server]
end// order.proto — satu file ini mendefinisikan kontrak untuk semua bahasa
syntax = "proto3";
package order;
service OrderService {
// Unary: buat satu order
rpc CreateOrder(CreateOrderRequest) returns (OrderResponse);
// Server streaming: pantau status order secara real-time
rpc WatchOrderStatus(WatchRequest) returns (stream OrderStatus);
// Client streaming: upload bulk order sekaligus
rpc BulkCreateOrders(stream CreateOrderRequest) returns (BulkResponse);
}
message CreateOrderRequest {
int64 user_id = 1;
repeated OrderItem items = 2;
}
message OrderItem {
int64 product_id = 1;
int32 quantity = 2;
}
message OrderResponse {
int64 order_id = 1;
string status = 2;
int64 created_at = 3;
}
WebSocket
WebSocket menyelesaikan masalah fundamental HTTP: ia adalah protokol request–response, yang artinya server tidak bisa mengirim data ke client tanpa dipancing terlebih dahulu oleh request dari client. Untuk aplikasi yang membutuhkan komunikasi real-time dua arah, ini adalah hambatan yang serius.
Cara Kerja
WebSocket dimulai sebagai HTTP request biasa (disebut “handshake”), lalu diupgrade menjadi koneksi persistent yang bisa digunakan keduanya untuk mengirim pesan kapan saja.
sequenceDiagram
participant C as Client (Browser / App)
participant S as Server
C->>S: HTTP GET /ws\nUpgrade: websocket\nConnection: Upgrade
S-->>C: 101 Switching Protocols
Note over C,S: Koneksi HTTP diupgrade ke WebSocket\nKoneksi persistent terbuka
S-->>C: {"type":"chat","msg":"Halo Alice!"}
C-->>S: {"type":"chat","msg":"Halo juga!"}
S-->>C: {"type":"notification","msg":"Bob bergabung"}
C-->>S: {"type":"typing","status":true}
Note over C,S: Keduanya bisa kirim kapan saja\ntanpa menunggu requestKapan WebSocket dan Bukan SSE
WebSocket adalah pilihan yang tepat ketika client juga perlu mengirim data secara frequent ke server — chat, multiplayer game, collaborative editing. Jika hanya server yang perlu push data ke client (monitoring dashboard, live feed), SSE lebih sederhana dan cukup.
WebSocket Server-Sent Events (SSE)
───────────────────────── ─────────────────────────
✓ Full-duplex ✓ Server-to-client only
✓ Binary dan text ✓ Text only (UTF-8)
✓ Custom subprotocol ✓ Built on HTTP (no upgrade)
✗ Lebih kompleks ✓ Auto-reconnect built-in
✗ Tidak otomatis reconnect ✓ HTTP/2 multiplexing
✗ Tidak bisa di-cache ✓ Bisa melewati HTTP proxy
Server-Sent Events (SSE)
SSE sering diremehkan karena tampak “lebih sederhana” dari WebSocket. Tapi kesederhanaannya adalah kekuatannya — ia berjalan di atas HTTP biasa, mendukung auto-reconnect secara native, dan bekerja baik dengan HTTP/2 multiplexing.
Cara Kerja
sequenceDiagram
participant C as Client (Browser)
participant S as Server
C->>S: GET /events\nAccept: text/event-stream
S-->>C: HTTP 200\nContent-Type: text/event-stream\nConection terbuka...
Note over S: Event terjadi
S-->>C: data: {"type":"price","symbol":"AAPL","value":182.5}\n\n
Note over S: Event lain terjadi
S-->>C: data: {"type":"price","symbol":"GOOG","value":175.2}\n\n
Note over C: Koneksi terputus (network issue)
C->>S: GET /events\nLast-Event-ID: 1234
Note over S: Resume dari event ID 1234
S-->>C: HTTP 200 — reconnected, resume dari checkpointSSE sangat cocok untuk use case seperti streaming output AI (seperti yang dilakukan ChatGPT), live log tailing, harga saham real-time, dan progress tracking untuk long-running job — semua kasus di mana server yang push data tapi client tidak perlu kirim balik secara frequent.
Webhook
Webhook adalah kebalikan dari polling. Alih-alih client bertanya ke server setiap beberapa detik “ada event baru?”, server yang mendatangi client ketika event terjadi. Ini adalah pola integrasi yang sangat efisien untuk event-driven system.
Cara Kerja
sequenceDiagram
participant User as User / Sistem Eksternal
participant PG as Payment Gateway
participant App as Aplikasi Kamu
participant DB as Database
User->>PG: Bayar invoice
PG->>PG: Proses pembayaran
Note over PG: Pembayaran berhasil
PG->>App: POST /webhooks/payment\n{"event":"payment.success","invoice_id":123,"amount":500000}
App->>App: Verifikasi signature HMAC
App->>DB: UPDATE invoice SET status='paid'
App->>App: Kirim email konfirmasi ke user
App-->>PG: 200 OK
Note over PG: Webhook dianggap berhasil diterimaYang Sering Dilupakan: Idempotency dan Verifikasi
Webhook bisa dikirim lebih dari sekali jika pengiriman pertama gagal atau timeout. Implementasi yang tidak memperhatikan ini bisa menyebabkan side effect yang berulang.
// BENAR: handler webhook yang idempotent dan memverifikasi signature
func handlePaymentWebhook(w http.ResponseWriter, r *http.Request) {
// 1. Verifikasi bahwa request benar-benar dari Stripe/payment gateway
payload, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
signature := r.Header.Get("Stripe-Signature")
if err := verifyWebhookSignature(payload, signature, webhookSecret); err != nil {
// 400 Bad Request — tolak webhook yang tidak terverifikasi
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
var event WebhookEvent
json.Unmarshal(payload, &event)
// 2. Idempotency check — cek apakah event ini sudah pernah diproses
if alreadyProcessed(event.ID) {
// 200 OK — acknowledge tapi tidak proses ulang
w.WriteHeader(http.StatusOK)
return
}
// 3. Proses event dan tandai sebagai sudah diproses secara atomik
processPaymentEvent(event)
markAsProcessed(event.ID)
w.WriteHeader(http.StatusOK)
}
// ANTI-PATTERN: tidak verifikasi signature, tidak cek duplikasi
// func handleWebhook(w http.ResponseWriter, r *http.Request) {
// var event WebhookEvent
// json.NewDecoder(r.Body).Decode(&event)
// processEvent(event) // ✗ bisa dieksekusi berulang kali
// }
Jangan pernah memproses webhook tanpa memverifikasi signature-nya. Endpoint webhook yang tidak diproteksi bisa dieksploitasi oleh pihak mana saja untuk memicu action di sistemmu — seperti menandai pembayaran sebagai sukses tanpa transaksi nyata.
Message Queue dan Event Streaming
Message Queue dan Event Streaming adalah backbone dari arsitektur yang truly asynchronous dan loosely coupled. Berbeda dari semua pola sebelumnya yang bersifat synchronous atau semi-synchronous, di sini producer dan consumer berjalan sepenuhnya independen satu sama lain.
Perbedaan Message Queue dan Event Streaming
Ini adalah perbedaan yang sering dikacaukan:
| Aspek | Message Queue (RabbitMQ, SQS) | Event Streaming (Kafka, Kinesis) |
|---|---|---|
| Model konsumsi | Message dihapus setelah dikonsumsi | Event tetap ada, bisa di-replay |
| Consumer | Biasanya satu consumer per message | Banyak consumer group independen |
| Ordering | Per-queue (terbatas) | Per-partition (kuat) |
| Retention | Sampai dikonsumsi | Berbasis waktu atau ukuran |
| Use case utama | Task queue, job distribution | Event sourcing, audit log, analytics |
| Throughput | Sedang | Sangat tinggi |
Cara Kerja
sequenceDiagram
participant OS as Order Service
participant MQ as Message Broker\n(Kafka / RabbitMQ)
participant NS as Notification Service
participant INV as Inventory Service
participant AN as Analytics Service
OS->>MQ: Publish event\n{"type":"order.created","orderId":999,...}
Note over MQ: Event disimpan dan\ndidistribusikan ke subscriber
MQ->>NS: Deliver event (Consumer Group A)
NS->>NS: Kirim email konfirmasi ke user
MQ->>INV: Deliver event (Consumer Group B)
INV->>INV: Kurangi stok produk
MQ->>AN: Deliver event (Consumer Group C)
AN->>AN: Catat ke data warehouse
Note over OS,AN: Order Service tidak tahu\nsiapa yang mengkonsumsi event-nyaKeindahan pola ini adalah decoupling yang sesungguhnya: Order Service tidak perlu tahu bahwa ada Notification Service, Inventory Service, dan Analytics Service. Ia hanya publish event — siapa yang mendengarkan adalah urusan konsumer masing-masing.
tRPC
tRPC adalah pendekatan yang sangat berbeda dari yang lain — ia tidak mendefinisikan protokol atau format baru, tapi memanfaatkan TypeScript type system untuk menciptakan pengalaman RPC yang type-safe antara frontend dan backend tanpa schema terpisah.
Motivasi
Masalah yang tRPC selesaikan adalah sangat spesifik: ketika frontend dan backend keduanya ditulis dalam TypeScript, kenapa harus ada schema terpisah (OpenAPI, GraphQL SDL) yang perlu di-generate, di-maintain, dan disinkronkan? tRPC menjawab: tidak perlu.
Cara Kerja
sequenceDiagram
participant FE as Frontend (Next.js)
participant BE as Backend (Node.js)
participant DB as Database
Note over FE,BE: Type inference langsung\nFrontend tahu exact shape response\ntanpa schema terpisah
FE->>BE: trpc.user.getById.query({ id: 42 })
BE->>DB: SELECT * FROM users WHERE id=42
DB-->>BE: Row data
BE-->>FE: { id: 42, name: "Alice", email: "..." }
Note over FE: TypeScript tahu persis\nstruktur response ini\ntanpa generate code apapun// Backend — definisi router
// server/routers/user.ts
import { z } from 'zod';
import { router, publicProcedure } from '../trpc';
export const userRouter = router({
getById: publicProcedure
.input(z.object({ id: z.number() }))
.query(async ({ input }) => {
// Return type ini secara otomatis ter-infer di frontend
return await db.user.findUnique({ where: { id: input.id } });
}),
update: publicProcedure
.input(z.object({
id: z.number(),
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input }) => {
return await db.user.update({
where: { id: input.id },
data: { name: input.name, email: input.email },
});
}),
});
// Frontend — konsumsi dengan full type safety, tanpa generate apapun
// components/UserProfile.tsx
const { data: user } = trpc.user.getById.useQuery({ id: 42 });
// TypeScript sudah tahu: user.name, user.email ada
// user.nonExistentField akan error di compile time
tRPC adalah alat yang sangat powerful untuk fullstack TypeScript project, tapi ia memiliki constraint yang tidak bisa diabaikan: client dan server harus menggunakan JavaScript/TypeScript runtime yang sama.
Perbandingan Menyeluruh
Tabel berikut merangkum semua dimensi penting dari setiap pola komunikasi untuk membantu perbandingan langsung.
| REST | GraphQL | gRPC | WebSocket | SSE | Webhook | Msg Queue | tRPC | |
|---|---|---|---|---|---|---|---|---|
| Arah komunikasi | C→S | C→S | C→S / stream | Dua arah | S→C | S→S | Async | C→S |
| Format data | JSON/XML | JSON | Protobuf (biner) | Bebas | Text | JSON | Bebas | JSON |
| Transport | HTTP/1.1+ | HTTP/1.1+ | HTTP/2 | WebSocket | HTTP | HTTP | TCP | HTTP |
| Type safety | Manual | Schema | Protobuf | Tidak | Tidak | Tidak | Tidak | TypeScript |
| Cacheability | ✓ Excellent | ✗ Sulit | ✗ Tidak | ✗ Tidak | ✗ Tidak | ✗ Tidak | ✗ Tidak | ✗ Tidak |
| Real-time | ✗ Polling | Subscription | Streaming | ✓ Native | ✓ Native | ✓ Event-driven | ✓ Async | ✗ Polling |
| Multi-bahasa | ✓ Universal | ✓ Universal | ✓ Universal | ✓ Universal | ✓ Universal | ✓ Universal | ✓ Universal | ✗ JS/TS only |
| Overhead setup | Rendah | Sedang | Tinggi | Sedang | Rendah | Rendah | Tinggi | Rendah |
| Cocok untuk | Public API | Frontend bervariasi | Internal service | Real-time chat | Live feed | Integrasi | Event-driven | Fullstack TS |
Decision Tree — Memilih yang Tepat
flowchart TD
START[Mulai dari sini] --> Q1{Stack kamu\nfullstack TypeScript?}
Q1 -- Ya, tim kecil --> TRPC[tRPC\n✓ Developer experience terbaik\nuntuk fullstack TS]
Q1 -- Tidak / Tim besar --> Q2
Q2{Komunikasi\nperlu real-time?} -- Tidak --> Q3
Q2 -- Ya --> Q4{Dua arah\natau server push saja?}
Q4 -- Dua arah penuh --> WS[WebSocket\n✓ Chat, game, collaborative]
Q4 -- Server push saja --> SSE_NODE[SSE\n✓ Live feed, monitoring,\nAI streaming output]
Q3{Siapa yang\nmengkonsumsi API?} -- Developer eksternal --> REST_NODE[REST\n✓ Public API, CRUD,\nkemudahan akses]
Q3 -- Frontend yang kompleks --> Q5
Q3 -- Service internal --> Q6
Q5{Banyak variasi\ndata per view?} -- Ya --> GQL[GraphQL\n✓ BFF pattern, mobile,\nrelasi data kompleks]
Q5 -- Tidak --> REST_NODE
Q6{Butuh response\nsynchronous?} -- Ya --> GRPC_NODE[gRPC\n✓ Internal microservice,\nperforma tinggi]
Q6 -- Tidak / Event-driven --> Q7
Q7{Butuh integrasi\ndengan sistem luar?} -- Ya --> WH[Webhook\n✓ Payment gateway,\nCICD, SaaS integration]
Q7 -- Tidak, internal --> MQ_NODE[Message Queue\n✓ Decoupled services,\nasync processing]
style TRPC fill:#e8f5e9,stroke:#43a047
style WS fill:#fff3e0,stroke:#fb8c00
style SSE_NODE fill:#fce4ec,stroke:#e91e63
style REST_NODE fill:#f3e5f5,stroke:#8e24aa
style GQL fill:#e3f2fd,stroke:#1e88e5
style GRPC_NODE fill:#e0f7fa,stroke:#00acc1
style WH fill:#fff8e1,stroke:#fdd835
style MQ_NODE fill:#e0f2f1,stroke:#00897bRekomendasi Berdasarkan Skenario Nyata
Aplikasi E-commerce (Web + Mobile)
Arsitektur yang tepat bukan memilih satu pola — tapi memadukan beberapa yang tepat untuk setiap kebutuhan.
flowchart LR
WEB[Web Frontend] -->|REST / GraphQL| API[API Gateway]
MOB[Mobile App] -->|GraphQL| API
API --> OS[Order Service]
API --> PS[Product Service]
API --> US[User Service]
OS -->|Publish event| MQ[(Kafka)]
MQ --> NS[Notification\nService]
MQ --> INV[Inventory\nService]
PG[Payment Gateway] -->|Webhook| OS
WEB <-->|WebSocket| NOTIF[Realtime\nNotification]- GraphQL untuk mobile app — data yang diambil minimal sesuai screen
- REST untuk Web yang lebih sederhana dan bisa memanfaatkan browser cache
- Webhook dari payment gateway untuk notifikasi pembayaran
- Kafka untuk propagasi event order ke service lain secara async
- WebSocket untuk notifikasi real-time ke browser
Platform SaaS Internal
REST → API publik untuk integrasi customer
gRPC → Komunikasi antar microservice internal
Kafka → Event bus untuk audit log dan analytics
Webhook → Notifikasi ke customer saat event terjadi di platform
SSE → Live log dan progress untuk long-running job di UI
Ringkasan
- Tidak ada satu pola yang cocok untuk semua use case — sistem production yang matang hampir selalu menggunakan beberapa pola sekaligus sesuai kebutuhannya masing-masing.
- REST adalah pilihan default yang solid untuk public API dan CRUD operations — matang, tooling berlimpah, dan cacheability-nya excellent.
- GraphQL cocok ketika frontend membutuhkan fleksibilitas tinggi dalam memilih data — tapi perlu perhatian ekstra pada N+1 problem dan query complexity.
- gRPC adalah pilihan terbaik untuk komunikasi internal antar service yang membutuhkan performa dan type safety — tapi tidak cocok untuk public API karena tooling browser yang terbatas.
- WebSocket untuk real-time dua arah; SSE untuk server push satu arah — pilih SSE jika tidak butuh komunikasi dari client ke server secara frequent karena lebih sederhana dan native di HTTP.
- Webhook harus selalu diverifikasi signature-nya dan handler-nya harus idempotent — pengiriman duplikat adalah perilaku normal yang harus diantisipasi.
- Message Queue vs Event Streaming — Queue untuk task distribution (pesan dihapus setelah dikonsumsi); Streaming untuk event log yang bisa di-replay oleh banyak consumer.
- tRPC adalah developer experience terbaik untuk fullstack TypeScript — tapi ia sangat opinionated dan hanya bisa digunakan dalam ekosistem JavaScript/TypeScript.