Membangun Real-Time Subscriptions dengan GraphQL Federation di Microservices
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap arsitektur microservices modern, tantangan terbesar bukanlah membangun layanan independen yang kecil, melainkan menyatukan layanan-layanan tersebut ke dalam pengalaman pengguna yang utuh dan responsif secara real-time. Pendekatan tradisional REST API sering kali berujung pada masalah API Gateway fatigue, di mana klien harus melakukan over-fetching atau under-fetching data melalui multiple round trips. Ketika kebutuhan bergeser ke real-time (seperti live tracking, chat system, dan live dashboard), arsitektur berbasis REST dan WebSocket terfragmentasi menjadi mimpi buruk pemeliharaan.
GraphQL Federation hadir untuk mengatasi fragmentasi ini dengan menggabungkan beberapa skema GraphQL independen dari berbagai microservice menjadi satu unified supergraph. Namun, tantangan teknis meningkat secara eksponensial ketika kita memperkenalkan fitur Real-Time Subscriptions ke dalam ekosistem federasi. Tidak seperti query atau mutation yang bersifat stateless, subscription memerlukan koneksi persisten (biasanya melalui WebSocket) dan infrastruktur pub/sub terdistribusi agar event dari satu microservice dapat dipancarkan ke gateway dan diteruskan ke klien di seluruh penjuru dunia.
Kondisi Sebelum vs Sesudah Penerapan
| Metrik / Aspek | Sebelum: REST + WebSocket Terfragmentasi | Sesudah: GraphQL Federation + Redis Pub/Sub | Dampak Arsitektural |
|---|---|---|---|
| Network Round Trips | Klien memanggil 4-6 endpoint REST berbeda + koneksi WebSocket terpisah per service. | 1 Endpoint Gateway terpusat untuk HTTP & WebSocket. | Pengurangan latensi jaringan hingga 65% pada klien mobile. |
| Data Payload | Over-fetching masif, klien menerima struktur JSON mentah yang tidak dibutuhkan. | Klien mendeklarasikan bentuk data yang presisi menggunakan skema terfederasi. | Penghematan bandwidth jaringan hingga 40%. |
| Manajemen State | Logika pub/sub tersebar di berbagai service tanpa standardisasi protokol. | Terpusat melalui Supergraph Gateway dengan Redis sebagai backbone pub/sub cluster. | Skalabilitas horizontal yang mulus tanpa single point of failure. |
2. Konsep Inti & Deep Dive Arsitektur
Arsitektur GraphQL Federation dengan Subscriptions melibatkan komponen kunci yang bekerja secara sinkron di balik layar:
- Router / Gateway: Titik masuk tunggal (biasanya menggunakan Apollo Router atau Hive Router berbasis Rust/Node.js) yang mengelola koneksi WebSocket dari klien, memparsing dokumen GraphQL, dan merutekan operasi ke subgraphs yang relevan.
- Subgraphs (Microservices): Layanan mandiri (misal:
Users Service,Orders Service,Notifications Service) yang mengekspos skema GraphQL mereka sendiri menggunakan direktif federasi seperti@key,@extends, dan@external. - Broker (Redis Pub/Sub): Lapisan perantara komunikasi asinkron. Ketika sebuah event terjadi di
Orders Service, service tersebut mempublikasikan pesan ke Redis channel. Router atau service penerima mendengarkan channel tersebut untuk meneruskan data ke klien yang melakukan subscription.
Diagram Alur Eksekusi Real-Time Subscriptions
+--------+ WebSocket Setup +------------------+
| Client | ---------------------------> | Apollo Router |
+--------+ +------------------+
| |
| 1. subscription { orderCreated } | 2. Setup subscription forward
v v
+--------+ +------------------+
| Client | | Orders Subgraph |
+--------+ +------------------+
^ |
| 5. Push payload via WebSocket | 3. Event triggered
| (via Gateway) v
+------------------+ +------------------+
| Apollo Router | <----------------- | Redis Pub |
+------------------+ 4. Pub/Sub Message +------------------+
3. Perbandingan Komparatif / Analisis Pendekatan
Untuk memilih arsitektur real-time di atas federasi, kita harus membandingkan tiga pendekatan utama yang sering dipertimbangkan oleh arsitek backend:
| Pendekatan | Penggunaan CPU & RAM | Latency (P99) | Kompleksitas Setup | Skalabilitas Horizontal |
|---|---|---|---|---|
| 1. WebSocket Langsung ke Microservice | Rendah di Gateway, Tinggi di masing-masing Service | Sangat Rendah (<20ms) | Tinggi (Klien harus tahu endpoint setiap service) | Buruk (Koneksi klien terikat ke instance spesifik) |
| 2. GraphQL Polling via Gateway | Sangat Tinggi (Beban database & CPU konstan) | Tinggi (>500ms) | Rendah | Sedang (Membebani database utama) |
| 3. GraphQL Federation + Redis Pub/Sub | Sedang-Tinggi di Gateway (Memory-bound untuk WS) | Rendah (<50ms) | Menengah-Tinggi | Sangat Baik (Stateless Gateway + Redis Cluster) |
[!WARNING] Hindari pendekatan polling untuk data real-time dalam skala besar karena akan melumpuhkan koneksi database relational Anda akibat lonjakan query yang konstan.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum mulai menulis kode, pastikan lingkungan infrastruktur Anda memenuhi spesifikasi berikut:
- OS: Ubuntu 22.04 LTS / Debian 12 (untuk server deployment) atau macOS Sonoma / WSL2 Ubuntu (untuk development).
- Runtime: Node.js v20.x LTS atau Go 1.22+.
- Broker: Redis Server v7.2+ (dioptimalkan dengan konfigurasi
maxmemorydanmaxmemory-policy volatile-lru). - Gateway: Apollo Router (Binary versi terbaru) atau Node.js berbasis
@apollo/gateway/graphql-yoga. - Database: PostgreSQL 15+ atau MySQL 8.0+ dengan dukungan ekstensi yang memadai.
Verifikasi instalasi Redis dan Node.js pada terminal Anda:
node -v
npm -v
redis-cli --version
5. Panduan Implementasi Langkah demi Langkah
Berikut adalah implementasi nyata dari sistem Supergraph dengan dua subgraphs (users dan orders) serta mekanisme real-time menggunakan Redis Pub/Sub.
Langkah 1: Setup Redis Broker dan Konfigurasi Dasar
Pastikan Redis berjalan di port default 6379. Buat file konfigurasi redis.conf yang dioptimalkan untuk pub/sub:
port 6379
protected-mode yes
maxmemory 512mb
maxmemory-policy noeviction
appendonly yes
Langkah 2: Implementasi Orders Subgraph (Node.js + Express + GraphQL Yoga + Redis)
Buat direktori orders-service, inisialisasi project, dan install dependencies:
mkdir orders-service && cd orders-service
npm init -y
npm install graphql @graphql-tools/schema graphql-yoga ioredis
Buat file server.js:
import { createServer } from 'http';
import { createPubSub, createSchema, createYoga } from 'graphql-yoga';
import { parse } from 'graphql';
import { Redis } from 'ioredis';
// Inisialisasi Redis Publisher dan Subscriber
const pubsub = createPubSub();
const redisPub = new Redis({ host: 'localhost', port: 6379 });
const redisSub = new Redis({ host: 'localhost', port: 6379 });
const CHANNEL_ORDER_CREATED = 'ORDER_CREATED';
// Subscribe ke Redis channel untuk mendistribusikan event antar instance service
redisSub.subscribe(CHANNEL_ORDER_CREATED, (err) => {
if (err) {
console.error('Failed to subscribe to Redis channel:', err);
} else {
console.log(`Subscribed to Redis channel: ${CHANNEL_ORDER_CREATED}`);
}
});
redisSub.on('message', (channel, message) => {
if (channel === CHANNEL_ORDER_CREATED) {
const order = JSON.parse(message);
pubsub.publish(CHANNEL_ORDER_CREATED, { orderCreated: order });
}
});
const orders = [
{ id: '1', item: 'MacBook Pro M3', userId: 'u1', total: 1999.99 },
{ id: '2', item: 'UltraWide Monitor', userId: 'u2', total: 499.99 }
];
const schema = createSchema({
typeDefs: /* GraphQL */ `
extend type Query {
order(id: ID!): Order
orders: [Order]
}
extend type Mutation {
createOrder(item: String!, userId: ID!, total: Float!): Order!
}
extend type Subscription {
orderCreated: Order!
}
type Order @key(fields: "id") {
id: ID!
item: String!
total: Float!
userId: ID!
}
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order]
}
`,
resolvers: {
Query: {
order: (_, { id }) => orders.find(o => o.id === id),
orders: () => orders,
},
Mutation: {
createOrder: async (_, { item, userId, total }) => {
const newOrder = { id: String(orders.length + 1), item, userId, total };
orders.push(newOrder);
// Publikasikan ke Redis agar seluruh instance service menerima event
await redisPub.publish(CHANNEL_ORDER_CREATED, JSON.stringify(newOrder));
return newOrder;
},
},
Subscription: {
orderCreated: {
subscribe: () => pubsub.asyncIterator([CHANNEL_ORDER_CREATED]),
},
},
User: {
orders(user) {
return orders.filter(o => o.userId === user.id);
},
},
},
});
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(4001, () => {
console.log('Orders Subgraph running on http://localhost:4001/graphql');
});
Langkah 3: Konfigurasi Apollo Router (Supergraph Gateway)
Buat file konfigurasi router.yaml untuk menyatukan subgraph:
sanitizer:
enabled: true
supergraph:
listen: 0.00.0:4000
introspection: true
subgraphs:
orders:
routing_url: http://localhost:4001/graphql
schema:
file: ./subgraphs/orders.graphql
6. Bedah Kode & Penjelasan Detail Baris Kunci
const pubsub = createPubSub(): Menggunakan engine asinkron internal darigraphql-yogauntuk mengelola async iterators yang dikirimkan ke klien WebSocket.redisSub.subscribe(...)&redisPub.publish(...): Pemisahan instance koneksi Redis (redisSubdanredisPub) adalah keharusan teknis dalam klien Redis (seperti ioredis). Ketika sebuah koneksi Redis berada dalam mode subscriber, koneksi tersebut tidak dapat menjalankan perintah lain sepertipublishatauget.- Direktif Federasi (
@key,@external): Memungkinkan subgraphordersmereferensikan tipeUseryang dimiliki oleh subgraph lain tanpa harus mendefinisikan ulang seluruh atribut pengguna.
7. Skenario Produksi & Studi Kasus Dunia Nyata
Dalam lingkungan produksi berskala enterprise (misalnya e-commerce flash sale dengan 50.000 concurrent WebSocket connections), arsitektur di atas harus diperluas:
- Redis Cluster: Mengganti standalone Redis dengan Redis Cluster untuk memastikan ketersediaan tinggi (high availability) dan distribusi beban pub/sub.
- Load Balancing WebSocket: Menggunakan Nginx di depan Apollo Router dengan konfigurasi khusus untuk mempertahankan koneksi long-lived TCP/WebSocket.
Contoh konfigurasi Nginx untuk Reverse Proxy & WebSocket Upstream:
upstream graphql_gateway {
server 127.0.0.1:4000;
keepalive 32;
}
server {
listen 80;
server_name api.enterprise.com;
location /graphql {
proxy_pass http://graphql_gateway;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
8. Common Pitfalls, Anti-Patterns & Solusi
- Pitfall 1: Memory Leak pada Redis Pub/Sub Subscriber
- Masalah: Lupa melakukan unsubscribe atau membersihkan listener saat service melakukan graceful shutdown.
- Solusi: Selalu tangkap sinyal
SIGTERMdanSIGINTuntuk menutup koneksi Redis secara aman.
- Pitfall 2: Menggunakan Koneksi Redis Tunggal untuk Pub dan Sub
- Masalah: Menyebabkan error
ERR Can't execute command while not in pub/sub stateatau sebaliknya. - Solusi: Buat dua instance klien Redis yang terisolasi untuk fungsi penerbit dan langganan.
- Masalah: Menyebabkan error
9. Benchmark, Metrik Kinerja & Tuning Performa
Pengujian beban (load testing) untuk layanan real-time memerlukan tool yang mendukung protokol WebSocket seperti artillery atau k6.
Contoh skrip pengujian k6 (load-test-ws.js):
import ws from 'k6/ws';
import { check } from 'k6';
export const options = {
vus: 100,
duration: '30s',
};
export default function () {
const url = 'ws://localhost:4000/graphql';
const params = { headers: { 'Sec-WebSocket-Protocol': 'graphql-transport-ws' } };
const res = ws.connect(url, params, function (socket) {
socket.on('open', function () {
socket.send(JSON.stringify({
type: 'connection_init',
}));
socket.send(JSON.stringify({
id: '1',
type: 'subscribe',
payload: {
query: 'subscription { orderCreated { id item total } }',
},
}));
});
socket.setTimeout(function () {
socket.close();
}, 10000);
});
check(res, { 'status is 101': (r) => r && r.status === 101 });
}
10. Checklist Penerapan Produksi & Kesimpulan
Sebelum merilis arsitektur GraphQL Federation dengan Subscriptions ke lingkungan produksi, pastikan checklist berikut terpenuhi:
- Pemisahan koneksi Redis Pub dan Sub telah diimplementasikan.
- Nginx / Load Balancer dikonfigurasi dengan
proxy_read_timeoutyang memadai untuk koneksi WebSocket. - Rate limiting diaktifkan pada endpoint GraphQL untuk mencegah serangan DoS melalui pembukaan koneksi WebSocket massal.
- Monitoring metrik CPU, Memory, dan jumlah koneksi aktif WebSocket menggunakan Prometheus & Grafana.
- Mekanisme Graceful Shutdown diuji pada seluruh instance subgraph.
Dengan menerapkan arsitektur ini secara disiplin, organisasi Anda dapat menikmati keunggulan skalabilitas microservices tanpa mengorbankan pengalaman pengguna yang instan dan responsif secara real-time.