π Topik
REST vs GraphQL vs gRPC: memilih berdasarkan beban nyata, bukan hype
π― Tujuan Belajar
Setelah sesi ini, keputusan berikut menjadi lebih baik:
- Memilih API style berdasarkan consumer, latency, coupling, dan observability.
- Membedakan kebutuhan public API, frontend aggregation, dan service-to-service RPC.
- Menghindari migrasi ke GraphQL atau gRPC hanya karena terlihat modern.
- Mendesain retry, idempotency, error, versioning, dan batas operasional sejak awal.
π§ Penjelasan Inti
REST
REST memakai resource dan HTTP semantics:
GET /orders/123
POST /orders
PATCH /orders/123
DELETE /orders/123
Kekuatan utama:
- Mudah dikonsumsi browser, mobile, partner, dan CLI.
- HTTP caching, status code, proxy, gateway, dan monitoring sudah matang.
- Kontrak mudah dipahami tanpa code generation.
- Cocok untuk public API dan resource-oriented domain.
Kelemahan:
- Client bisa menerima terlalu banyak atau terlalu sedikit data.
- Aggregation lintas resource memerlukan banyak request atau endpoint khusus.
- Kontrak sering menjadi dokumentasi eksternal, bukan enforcement compile-time.
- Perbedaan interpretasi status code dan error mudah muncul.
GraphQL
GraphQL memberi client kemampuan menentukan shape response:
query {
order(id: "123") {
id
status
customer {
name
}
items {
productName
quantity
}
}
}
Kekuatan utama:
- Satu endpoint dapat melayani banyak kebutuhan UI.
- Mengurangi over-fetching dan under-fetching.
- Schema typed dan introspectable.
- Cocok untuk frontend dengan screen yang cepat berubah.
Kelemahan:
- Query complexity sulit diprediksi.
- Caching HTTP standar lebih sulit.
- Authorization harus diterapkan pada field atau resolver, bukan hanya endpoint.
- Resolver dapat menghasilkan N+1 query.
- Debugging performa memerlukan tracing sampai level resolver.
- Federation menambah router, schema composition, ownership, dan failure mode baru.
gRPC
gRPC memakai RPC method dan Protocol Buffers:
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc CreateOrder(CreateOrderRequest) returns (Order);
}
Kekuatan utama:
- Protobuf binary biasanya lebih kecil dan cepat diparse daripada JSON.
- Contract-first dan code generation mengurangi boilerplate.
- HTTP/2 mendukung multiplexing dan streaming.
- Sangat cocok untuk service-to-service dengan kontrak internal yang stabil.
- Unary, server-streaming, client-streaming, dan bidirectional streaming tersedia.
Kelemahan:
- Browser tidak dapat berbicara gRPC native secara langsung karena batasan browser terhadap HTTP/2 trailers dan kontrol transport; perlu gRPC-Web, Connect, atau proxy.
- Binary payload kurang nyaman untuk debugging manual.
- Evolusi schema membutuhkan disiplin field number dan compatibility.
- Public API membutuhkan tooling tambahan untuk konsumen non-gRPC.
- HTTP/2 memakai TCP; packet loss dapat menyebabkan head-of-line blocking pada seluruh multiplexed streams.
π Perbandingan
| Aspek | REST | GraphQL | gRPC |
|---|---|---|---|
| Model | Resource | Query graph | Remote procedure |
| Payload umum | JSON | JSON | Protobuf binary |
| Consumer utama | Public API, browser, partner | Frontend kompleks | Internal services |
| Contract | OpenAPI/documentation | Schema | .proto |
| Code generation | Opsional | Opsional | Umumnya dipakai |
| Caching HTTP | Mudah | Sulit, perlu strategi tambahan | Bukan kekuatan utama |
| Streaming | Terbatas; SSE/WebSocket terpisah | Subscription | Native |
| Debugging manual | Mudah | Sedang | Lebih sulit |
| Query flexibility | Rendahβsedang | Tinggi | Rendah |
| Latency predictability | Tinggi | Bisa rendah bila query kompleks | Tinggi untuk RPC terkontrol |
| Browser support | Native | Native | Perlu adapter |
| Coupling | Resource/schema coupling | Schema dan query coupling | Strong contract coupling |
| Operasional | Paling sederhana | Resolver/router kompleks | Proxy, timeout, channel, schema kompleks |
| Cocok untuk | Public boundary | UI aggregation | Backend-to-backend |
βοΈ Cara Kerja Internal
REST request flow
Client
β HTTP request
βΌ
Load Balancer / API Gateway
β route berdasarkan method + path
βΌ
REST Handler
β validate input
βΌ
Application Service
β business rule
βΌ
Database / external service
β
βΌ
HTTP status + JSON response
HTTP semantics menjadi bagian penting kontrak:
200 OK: operasi sukses.201 Created: resource baru dibuat.202 Accepted: pekerjaan diterima, belum selesai.204 No Content: sukses tanpa body.400 Bad Request: request invalid.401 Unauthorized: identity belum terautentikasi.403 Forbidden: identity ada, tetapi tidak punya izin.404 Not Found: resource tidak tersedia atau sengaja disamarkan.409 Conflict: bentrok dengan state saat ini.429 Too Many Requests: rate limit.5xx: kegagalan server atau dependency.
Status code bukan dekorasi. Client memakai status tersebut untuk memutuskan retry, fallback, atau menampilkan error.
GraphQL request flow
Client query
β
βΌ
GraphQL parser
β parse + validate terhadap schema
βΌ
Auth + query depth/complexity check
β
βΌ
Resolver execution plan
βββ User service
βββ Order service
βββ Product service
β
βΌ
DataLoader / batching
β
βΌ
Response sesuai selection set
Contoh masalah N+1:
Query orders
βββ resolve order 1 β query customer 1
βββ resolve order 2 β query customer 2
βββ resolve order 3 β query customer 3
βββ ...
Dengan batching:
resolve orders
βββ DataLoader([customer_id_1, customer_id_2, customer_id_3])
β satu query batch
DataLoader mengurangi jumlah query, tetapi tidak otomatis menyelesaikan:
- Query database terlalu berat.
- Dependency lambat.
- Query depth tidak dibatasi.
- Authorization field-level salah.
- Resolver memanggil service yang sama berkali-kali dengan parameter berbeda.
gRPC request flow
Client stub
β generated method
βΌ
HTTP/2 connection
β protobuf frames
βΌ
gRPC server interceptor
β auth, deadline, tracing
βΌ
Service method
β
βΌ
Database / dependency
β
βΌ
Status code + protobuf response + trailers
Kontrak Protobuf harus berevolusi secara kompatibel:
message Order {
string id = 1;
string status = 2;
}
Aturan penting:
- Jangan memakai ulang field number yang sudah pernah digunakan.
- Hapus field dengan
reserved. - Tambah field baru dengan number baru.
- Client lama harus tetap dapat membaca response baru.
- Server baru harus menangani field yang tidak dikenal dari client lama.
message Order {
reserved 3;
string id = 1;
string status = 2;
string currency = 4;
}
ποΈ Decision Framework
Pilih berdasarkan pertanyaan ini:
1. Siapa consumer API?
| Consumer | Default awal |
|---|---|
| Browser publik | REST atau GraphQL |
| Mobile dengan kebutuhan screen kompleks | REST+BFF atau GraphQL |
| Internal backend | gRPC atau REST |
| External partner heterogen | REST |
| Data streaming | gRPC streaming, SSE, atau WebSocket |
| Banyak bahasa dan tooling sederhana | REST/JSON |
| Controlled polyglot environment | gRPC/Protobuf |
2. Apakah response shape berubah cepat?
- Stabil dan resource-oriented β REST.
- Berubah mengikuti banyak screen β GraphQL atau REST+BFF.
- Stabil dan dimiliki tim backend β gRPC.
3. Apakah latency dan throughput sangat penting?
- RPC kecil, sering, internal, typed β gRPC layak.
- Public API dengan network tidak stabil β REST JSON sering lebih mudah dioperasikan.
- GraphQL perlu mengontrol complexity; query fleksibel tidak sama dengan query murah.
4. Siapa memiliki aggregation logic?
REST tanpa BFF
Mobile β /user
Mobile β /orders
Mobile β /recommendations
Banyak round trip dan client memegang orchestration.
REST dengan BFF
Mobile β Mobile BFF
βββ User service
βββ Order service
βββ Recommendation service
GraphQL gateway
Frontend β GraphQL Router
βββ User subgraph
βββ Order subgraph
βββ Product subgraph
BFF lebih eksplisit dan biasanya lebih mudah dikontrol. GraphQL memberi flexibility lebih besar, tetapi menambah runtime query engine.
βοΈ Trade-off & Kapan JANGAN Dipakai
Jangan memilih REST jika
- UI membutuhkan banyak agregasi lintas domain dan setiap perubahan memaksa penambahan endpoint.
- Client membutuhkan selection set sangat berbeda.
- Streaming adalah kebutuhan utama, bukan tambahan.
Namun, jangan menganggap GraphQL satu-satunya solusi. REST+BFF sering cukup.
Jangan memilih GraphQL jika
- API hanya CRUD sederhana dengan resource stabil.
- HTTP caching dan CDN adalah strategi utama.
- Tim belum siap mengelola resolver performance, query cost, schema governance, dan observability.
- Tidak ada kebutuhan nyata untuk client-driven query.
- Security model hanya dipahami pada level endpoint.
GraphQL bukan database query proxy. Membiarkan client mengirim query bebas ke database adalah desain buruk.
Jangan memilih gRPC jika
- Consumer utama browser publik.
- Partner eksternal memakai bahasa atau tooling sangat beragam.
- API harus mudah dites dengan
curl. - Organisasi belum siap mengelola generated code, deadlines, status mapping, dan compatibility.
- Tidak ada kebutuhan latency, streaming, atau contract enforcement yang membenarkan kompleksitasnya.
Jangan membuat tiga API style tanpa boundary jelas
Contoh buruk:
Frontend β GraphQL
Mobile β REST
Service β gRPC
Partner β REST
Semua endpoint memiliki business logic sendiri
Risiko:
- Authorization berbeda antar protocol.
- Semantics error tidak konsisten.
- Business rule terduplikasi.
- Observability terfragmentasi.
- Perubahan domain harus dipelihara di banyak layer.
Gunakan satu application/domain core bila beberapa transport memang diperlukan:
REST Handler ββ
GraphQL Resolver ββΌββ Application Service ββ Domain
gRPC Handler ββββ
Transport adapter boleh berbeda. Business rule jangan.
π Idempotency dan Retry
Retry aman hanya jika operasi idempotent atau server memiliki mekanisme deduplikasi.
Masalah
Client β POST /payments
Server β payment berhasil
Network β response hilang
Client β retry POST /payments
Server β payment kedua dibuat
Solusi idempotency key
POST /payments
Idempotency-Key: payment-attempt-7f3a
Server menyimpan:
(idempotency_key, request_hash, status, response)
Alur:
key belum ada
β proses transaksi
β simpan response berdasarkan key
β kirim response
key sama + request sama
β kembalikan response tersimpan
key sama + request berbeda
β 409 Conflict
Pseudocode:
def create_payment(req, key):
previous = idempotency_store.get(key)
if previous:
if previous.request_hash != hash_request(req):
raise Conflict("idempotency key reused with different request")
return previous.response
with transaction():
reserve_key(key, hash_request(req))
payment = charge(req)
response = serialize(payment)
save_result(key, response)
return response
Hal penting:
- Idempotency key harus memiliki scope, misalnya per tenant atau account.
- Simpan hasil sukses dan error yang aman untuk diulang.
- Tentukan retention period.
- Jangan retry
POSTtanpa kontrak idempotency. - Gunakan deadline dan exponential backoff dengan jitter.
- Jangan retry semua
5xx; overload dapat makin parah.
Stripe mendokumentasikan pola idempotency key: request ulang dengan key yang sama dapat mengembalikan response sebelumnya. [1]
π Contoh Produksi & Failure Mode
REST public API
Arsitektur
Client
β
API Gateway
β
REST API
βββ Auth
βββ Rate limit
βββ OpenAPI contract
βββ Application services
Failure mode
- Client retry
POSTtanpa idempotency. PUTdipakai sebagai partial update, semantics menjadi ambigu.- Pagination memakai offset pada dataset besar; data bergeser saat insert.
- API mengembalikan
200untuk semua error; client tidak bisa membedakan retryable error. - Versioning dilakukan dengan menyalin seluruh service tanpa deprecation plan.
Untuk dataset yang berubah cepat, cursor pagination lebih stabil:
GET /orders?limit=50&after=eyJpZCI6IjEwMCJ9
GraphQL frontend aggregation
Failure mode
- Query valid secara schema tetapi mahal secara runtime.
- Resolver menyebabkan N+1.
- Satu dependency lambat menahan seluruh response.
- Field sensitif dapat diakses karena authorization hanya diterapkan pada root query.
- Query introspection atau complexity tidak dibatasi pada endpoint publik.
- Federation router menjadi single point of failure.
- Subgraph ownership tidak jelas; schema berubah dan merusak consumer lain.
Mitigasi:
- Query depth dan complexity limit.
- Persisted queries untuk client terkontrol.
- DataLoader atau batching.
- Per-field timeout dan tracing.
- Rate limit berdasarkan cost, bukan jumlah request saja.
- Schema linting dan backward compatibility checks.
gRPC internal services
Failure mode
- Tidak menetapkan deadline; request menggantung dan menghabiskan thread/event-loop.
- Retry berlapis di client, proxy, dan service; traffic meledak saat dependency gagal.
- Channel terlalu sedikit atau terlalu banyak.
- Error gRPC dipetakan buruk menjadi HTTP.
- Protobuf field number dipakai ulang.
- Browser dipaksa mengakses gRPC native tanpa adapter.
- HTTP/2 connection terkena transport-level head-of-line blocking saat packet loss.
Mitigasi:
deadline
+ bounded retry
+ jitter
+ circuit breaker
+ connection health check
+ tracing
+ load balancing
HTTP/2 menghilangkan application-level head-of-line blocking antar request, tetapi TCP tetap dapat menahan stream lain ketika packet loss terjadi. [2]
β οΈ Miskonsepsi Umum
-
βGraphQL selalu lebih cepat daripada REST.β
Salah. GraphQL mengurangi round trip atau payload berlebih, tetapi resolver chain dapat lebih lambat. -
βgRPC selalu lebih cepat karena binary.β
Salah. Network latency, database, serialization, queueing, dan dependency biasanya lebih dominan daripada format payload. -
βREST berarti tanpa schema.β
Salah. REST tetap membutuhkan kontrak; OpenAPI dapat menjadi source of truth. -
βGraphQL menghilangkan over-fetching.β
Hanya pada response selection. Resolver bisa tetap mengambil kolom atau data berlebih dari database. -
βgRPC cocok untuk browser karena HTTP/2.β
Tidak otomatis. Browser tidak memberi aplikasi kontrol penuh atas trailers dan transport yang diperlukan gRPC native. Gunakan gRPC-Web, Connect, atau gateway. [3] -
βStatus
200dengan{error: ...}lebih sederhana.β
Tidak. Ini merusak retry, monitoring, client behavior, dan alerting. -
βFederation sama dengan microservices.β
Tidak. Federation mengatur composition API/schema. Itu tidak otomatis memberi autonomous deployment, isolated data ownership, atau independent scalability. -
βSatu endpoint GraphQL berarti satu backend call.β
Tidak. Satu request dapat memicu puluhan atau ratusan resolver/dependency call. -
βPOST tidak bisa idempotent.β
Semantics default-nya tidak idempotent, tetapi dapat dibuat idempotent dengan idempotency key dan deduplication.
π Status Pengetahuan
DIKETAHUI
- REST unggul pada interoperability, HTTP tooling, dan public API.
- GraphQL unggul pada client-driven response shape, tetapi menambah runtime query complexity.
- gRPC unggul pada typed internal RPC, streaming, dan code generation.
- Retry membutuhkan deadline, backoff, dan idempotency strategy.
- HTTP/2 multiplexing tidak menghapus TCP-level head-of-line blocking.
DIINFERENSI
- Untuk tim kecil dengan CRUD dan satu-dua client, REST kemungkinan memberi total cost terendah.
- Untuk frontend dengan banyak screen dan backend domain terpisah, REST+BFF sering menjadi langkah lebih sederhana sebelum GraphQL.
- Untuk platform internal dengan banyak service dan kontrak stabil, gRPC memberi manfaat terbesar ketika generated contract benar-benar dipakai.
BELUM PASTI TANPA DATA SISTEM
- Protocol mana paling cepat pada workload tertentu.
- Biaya operasional GraphQL federation dibanding BFF.
- Batas query complexity yang aman.
- Apakah gRPC menurunkan latency secara material pada deployment tertentu.
Benchmark harus memakai traffic, payload, dependency, network topology, dan failure scenario nyata. Angka dari benchmark vendor tidak cukup.
π§ Decision Tree
Public API atau partner eksternal?
βββ Ya
β βββ Resource stabil β REST
β βββ Banyak client dengan kebutuhan shape berbeda β GraphQL atau REST+BFF
β
βββ Internal service?
βββ Streaming / strict typed contract / polyglot controlled β gRPC
βββ Simple CRUD / debugging mudah / tooling umum β REST
βββ Belum jelas β mulai REST; ukur kebutuhan sebelum menambah protocol
Default bukan keputusan final. Default adalah titik awal murah untuk validasi.
β Cek Pemahaman
-
Sebuah payment API kehilangan response setelah charge sukses. Mengapa retry biasa berbahaya, dan bagaimana idempotency key mencegah double charge?
-
GraphQL query mengambil 100 order, masing-masing customer berbeda. Failure mode apa yang mungkin muncul, dan mekanisme apa yang menguranginya?
-
Backend internal memiliki service Go, Java, dan Rust; browser adalah consumer utama. Apakah gRPC native menjadi pilihan tepat? Jelaskan boundary protocol yang lebih masuk akal.
π Poin Kunci
- Pilih protocol dari consumer, coupling, failure model, dan operational maturity.
- REST: interoperable dan mudah dioperasikan.
- GraphQL: fleksibel untuk client, tetapi query cost dan resolver complexity menjadi tanggung jawab server.
- gRPC: kuat untuk internal RPC, tetapi bukan default public browser API.
- BFF sering memberi sebagian manfaat GraphQL dengan boundary dan cost yang lebih eksplisit.
- Idempotency bukan fitur kosmetik; wajib untuk retryable command seperti payment atau order creation.
- Deadline wajib. Retry tanpa deadline dan budget adalah traffic amplifier.
- Satu domain core dapat memiliki REST, GraphQL, dan gRPC adapter tanpa menduplikasi business rule.
- Ukur workload nyata. Payload binary tidak otomatis mengalahkan bottleneck database atau network.
β‘οΈ Eksplorasi Lanjut
- API versioning: URI, header, media type, dan schema evolution.
- GraphQL query cost analysis, DataLoader, persisted queries, dan federation.
- gRPC deadlines, status codes, interceptors, health checking, dan load balancing.
- BFF versus API Gateway versus GraphQL Gateway.
- Contract testing dengan OpenAPI dan Protobuf.
- Distributed tracing lintas REST, GraphQL resolver, dan gRPC method.
- Idempotent consumer pada event-driven architecture.
π Sumber
[1] Stripe. βIdempotent requestsβ β Stripe API Documentation.
https://docs.stripe.com/api/idempotent_requests
[2] Salesforce Engineering. βThe Full Picture on HTTP/2 and HOL Blockingβ.
https://engineering.salesforce.com/the-full-picture-on-http-2-and-hol-blocking-7f964b34d205/
[3] Connect RPC. βFAQs β Why do I need a proxy to call gRPC backends?β
https://connectrpc.com/docs/faq/
[4] Buf. βConnect-Web: Itβs time for Protobuf and gRPC to be your first choice in the browserβ.
https://buf.build/blog/connect-web-protobuf-grpc-in-the-browser