L
REST API Design • Literally Web ID

Desain RESTful API Skala Enterprise dengan Pola CQRS dan Rate Limiting

⏱️ 15 menit baca • 📅 16 Oct 2026 • 👁️ 1 views
Desain RESTful API Skala Enterprise dengan Pola CQRS dan Rate Limiting

1. Konteks Industri & Analisis Akar Masalah

Dalam lanskap arsitektur sistem skala enterprise modern, monolith application dengan akses basis data relasional terpusat telah mencapai batas maksimal skalabilitasnya. Ketika platform digital melayani jutaan concurrent requests setiap detik, bottleneck performa tidak lagi sekadar terletak pada kapasitas CPU atau RAM server, melainkan pada bagaimana siklus baca (read) dan tulis (write) berebut sumber daya secara bersamaan pada lapisan basis data dan aplikasi.

Masalah klasik yang sering dihadapi oleh backend engineer adalah ketika tabel operasional utama menerima beban heavy write (transaksi finansial, log aktivitas, pembuatan pesanan) secara simultan dengan heavy read (pencarian katalog produk, laporan analitik, dasbor pengguna). Pendekatan Traditional CRUD (Create, Read, Update, Delete) menggunakan satu model data tunggal memaksa basis data melakukan kompromi yang merugikan:

  • Kunci Baris (Row-level Locking) & Kontensi (Contention): Operasi tulis yang sering mengakibatkan antrean pada operasi baca, memicu lonjakan latensi (p99 latency spikes).
  • Kompleksitas Query & Join: Permintaan baca yang kompleks memerlukan multi-table joins yang berat, membebani CPU database dan menghabiskan connection pool backend.
  • Skalabilitas Asimetris: Kebutuhan sumber daya untuk membaca data (yang bisa di-cache dengan agresif) jauh lebih besar daripada kebutuhan menulis. Skalabilitas infrastruktur menjadi tidak efisien karena harus menskalakan seluruh stack secara vertikal maupun horizontal secara bersamaan.

Sebelum penerapan CQRS (Command Query Responsibility Segregation) dan mekanisme Rate Limiting yang presisi di tingkat Edge (Cloudflare/Nginx), sistem enterprise sering mengalami Cascading Failures saat terjadi lonjakan trafik mendadak (flash sale atau serangan DDoS).

Kondisi Sebelum:

  • Single Database Instance untuk Read/Write.
  • Lonjakan traffic langsung membebani database utama, menyebabkan Connection Timeout.
  • Pembatasan akses berbasis IP sederhana yang mudah dibypass oleh botnets terdistribusi.
  • Skala API yang kaku, menyulitkan evolusi skema data tanpa merusak klien lama.

Kondisi Sesudah:

  • Pemisahan total antara model Command (Write) dan Query (Read) dengan basis data terpisah (misalnya MySQL master untuk write, Redis/Elasticsearch/MySQL read replica untuk read).
  • Proteksi berlapis dari Edge hingga Application Layer menggunakan Token Bucket Algorithm yang terdistribusi.
  • Latensi stabil (sub-15ms p99) berkat optimalisasi Read Model yang didenormalisasi.

2. Konsep Inti & Deep Dive Arsitektur

CQRS pada dasarnya adalah pemisahan model yang memperbarui data (Commands) dari model yang membaca data (Queries). Dalam konteks API Enterprise, ini berarti kita mendeksplit arsitektur menjadi dua alur eksekusi yang berbeda secara logis dan fisik.

+-----------------------------------------------------------------------------------------+
|                                      CLIENT LAYER                                       |
+-----------------------------------------------------------------------------------------+
                                             |
                                             v
+-----------------------------------------------------------------------------------------+
|                                  EDGE & WAF (CLOUDFLARE)                                |
|                               - Rate Limiting (Token Bucket)                            |
|                               - SSL Termination & Bot Mitigation                        |
+-----------------------------------------------------------------------------------------+
                                             |
                                             v
+-----------------------------------------------------------------------------------------+
|                             WEB SERVER / REVERSE PROXY (NGINX)                          |
|                             - Upstream Load Balancing                                   |
+------------------------------------+----------------------------------------------------+
                                     |                                      |
                     (POST/PUT/DELETE)       |                      (GET)           |     
                                     v                                      v
+------------------------------------------+    +-----------------------------------------+
|          COMMAND API SERVICE             |    |           QUERY API SERVICE             | 
|          (Go / Node.js / PHP)            |    |          (Go / Node.js / PHP)           | 
+------------------------------------------+    +-----------------------------------------+
                     |                                               |
                     v                                               v
+------------------------------------------+    +-----------------------------------------+
|       MESSAGE QUEUE (RabbitMQ / SQS)     |    |         READ REPLICA / CACHE            | 
+------------------------------------------+    |       (Redis / Elasticsearch / MySQL)   |
                     |                          +-----------------------------------------+
                     v                                               ^
+------------------------------------------+                         |
|         ASYNC EVENT WORKER               |                         |
+------------------------------------------+                         |
                     |                                               |
                     +---------------------u003e------------------------+
                                     DATABASE SYNC

Alur Eksekusi Data (Data Flow)

  1. Command Flow (Write): Klien mengirimkan HTTP POST/PUT/DELETE ke Command API. API ini melakukan validasi bisnis yang ketat, mengeksekusi perubahan pada Command Database (Master MySQL), dan menghasilkan Domain Event yang dikirim ke Message Broker (RabbitMQ/Kafka) untuk sinkronisasi asinkron.
  2. Query Flow (Read): Klien mengirimkan HTTP GET ke Query API. API ini langsung mengambil data dari Read Model yang telah dioptimalkan (Redis Cache atau MySQL Read Replica yang sudah didenormalisasi) tanpa pernah menyentuh tabel transaksional utama.
  3. Rate Limiting Layer: Setiap permintaan dari klien terlebih dahulu divalidasi oleh Rate Limiter berbasis Redis di tingkat Nginx atau API Gateway untuk memastikan tidak ada eksploitasi sumber daya.

3. Perbandingan Komparatif / Analisis Pendekatan

Berikut adalah analisis perbandingan mendalam antara arsitektur tradisional CRUD dengan arsitektur CQRS berbasis Enterprise:

Parameter Perbandingan Arsitektur Tradisional CRUD Enterprise CQRS + Rate Limiting Trade-off / Dampak Sumber Daya
Skalabilitas Baca/Tulis Coupled (Tergantung pada satu skema database). Decoupled (Bisa menskalakan read replica secara independen). CPU/RAM: CQRS membutuhkan memori lebih besar untuk caching dan storage replika.
Kompleksitas Pengembangan Rendah. Model data langsung dipetakan ke ORM. Tinggi. Membutuhkan penanganan sinkronisasi event (eventual consistency). Development Time: CQRS memperlambat fase awal pengembangan demi stabilitas jangka panjang.
Latency (p99) Tinggi saat terjadi lonjakan write contention. Sangat rendah (sub-20ms) berkat Read Models terdenormalisasi. Network Bandwidth: Sedikit meningkat karena komunikasi asinkron antar layanan.
Keamanan & Proteksi Bergantung pada middleware aplikasi standar. Dilindungi Token Bucket Rate Limiting terdistribusi di Edge & Nginx. Infrastructure Cost: Memerlukan komponen tambahan (Redis, Message Broker).

4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum membangun implementasi, pastikan lingkungan server (Ubuntu 22.04 LTS / Debian 12) memenuhi spesifikasi dan dependensi berikut:

  • OS: Linux Kernel 5.15+ (mendukung performa jaringan tinggi dengan epoll).
  • Web Server: Nginx (versi terbaru dengan modul Limit Req).
  • Backend Runtime: Go 1.21+ atau Node.js 20+ LTS.
  • Database & Cache: MySQL 8.0+ (Master-Replica setup) dan Redis 7.x (untuk Rate Limiting & Read Model Caching).
  • Queue Worker: Supervisor (untuk mengelola proses asynchronous worker).

Perintah persiapan dasar sistem:

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx redis-server mysql-server supervisor curl git build-essential
sudo systemctl enable --now nginx redis-server mysql

5. Panduan Implementasi Langkah demi Langkah

Langkah 1: Konfigurasi Nginx dengan Rate Limiting Terdistribusi

Buat file konfigurasi Nginx untuk menerapkan pembatasan laju (Rate Limiting) berbasis IP dan Token Bucket.

# /etc/nginx/conf.d/rate_limit.conf

# Mendefinisikan zone memori 10MB untuk menyimpan status rate limit berdasarkan IP klien
# 1MB dapat menampung sekitar 16.000 state IP address.
limit_req_zone $binary_remote_addr zone=api_write_limit:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api_read_limit:10m rate=50r/s;

server {
    listen 80;
    server_name api.enterprise.internal;

    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.enterprise.internal;

    ssl_certificate /etc/letsencrypt/live/api.enterprise.internal/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.enterprise.internal/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Command Endpoint (Write Operations) - Strict Rate Limit
    location /api/v1/commands/ {
        limit_req zone=api_write_limit burst=10 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Query Endpoint (Read Operations) - Relaxed Rate Limit
    location /api/v1/queries/ {
        limit_req zone=api_read_limit burst=50 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8082;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Langkah 2: Implementasi Backend Command Service (Go)

Berikut adalah implementasi Command Service menggunakan bahasa Go yang menerima instruksi tulis, menyimpannya ke database master, dan mempublikasikan event.

// main.go - Command Service
package main

import (
	"database/sql"
	"encoding/json"
	"log"
	"net/http"

	_ "github.com/go-sql-driver/mysql"
	"github.com/gomodule/redigo/redis"
)

var db *sql.DB
var redisPool *redis.Pool

type CreateOrderCommand struct {
	CustomerID string  `json:"customer_id"`
	Total      float64 `json:"total"`
}

func initRedis() {
	redisPool = &redis.Pool{
		MaxIdle:   80,
		MaxActive: 12000,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", "127.0.0.1:6379")
		},
	}
}

func CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var cmd CreateOrderCommand
	err := json.NewDecoder(r.Body).Decode(&cmd)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Simpan ke Master Database (Write Model)
	result, err := db.Exec("INSERT INTO orders (customer_id, total, status, created_at) VALUES (?, ?, 'PENDING', NOW())", cmd.CustomerID, cmd.Total)
	if err != nil {
		http.Error(w, "Database error", http.StatusInternalServerError)
		return
	}

	orderID, _ := result.LastInsertId()

	// Publish Event ke Redis Pub/Sub untuk Sinkronisasi Read Model
	conn := redisPool.Get()
	defer conn.Close()

	eventData, _ := json.Marshal(map[string]interface{}{
		"event":       "OrderCreated",
		"order_id":    orderID,
		"customer_id": cmd.CustomerID,
		"total":       cmd.Total,
	})
	_, err = conn.Do("PUBLISH", "order_events", eventData)
	if err != nil {
		log.Printf("Failed to publish event: %v", err)
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(map[string]interface{}{
		"status":   "success",
		"order_id": orderID,
	})
}

func main() {
	var err error
	db, err = sql.Open("mysql", "root:secure_password@tcp(127.0.0.1:3306)/enterprise_db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	initRedis()

	http.HandleFunc("/api/v1/commands/orders", CreateOrderHandler)
	log.Println("Command Service running on port 8081...")
	log.Fatal(http.ListenAndServe(":8081", nil))
}

Langkah 3: Implementasi Backend Query Service (Go)

Query Service membaca langsung dari Redis Read Model atau MySQL Read Replica yang dioptimalkan untuk kecepatan baca tinggi.

// query_service.go - Query Service
package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func initRedis() {
	redisPool = &redis.Pool{
		MaxIdle:   80,
		MaxActive: 12000,
		Dial: func() (redis.Conn, error) {
			return redis.Dial("tcp", "127.0.0.1:6379")
		},
	}
}

func GetOrderHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	orderID := r.URL.Query().Get("order_id")
	if orderID == "" {
		http.Error(w, "Missing order_id parameter", http.StatusBadRequest)
		return
	}

	conn := redisPool.Get()
	defer conn.Close()

	// Mengambil data langsung dari Read Cache (Denormalized Model)
	data, err := redis.Bytes(conn.Do("GET", "order:"+orderID))
	if err != nil {
		http.Error(w, "Order not found", http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	w.Write(data)
}

func main() {
	initRedis()

	http.HandleFunc("/api/v1/queries/orders", GetOrderHandler)
	log.Println("Query Service running on port 8082...")
	log.Fatal(http.ListenAndServe(":8082", nil))
}

6. Bedah Kode & Penjelasan Detail Baris Kunci

  1. limit_req_zone $binary_remote_addr zone=api_write_limit:10m rate=5r/s;

    • Baris ini pada konfigurasi Nginx mengalokasikan memori bersama sebesar 10MB (zone=...:10m) untuk melacak permintaan dari alamat IP unik ($binary_remote_addr). Opsi rate=5r/s membatasi klien hanya dapat melakukan 5 permintaan per detik untuk operasi Command guna mencegah eksploitasi serangan * brute force* atau DDoS. Parameter burst=10 pada limit_req mengizinkan lonjakan sementara hingga 10 request sebelum Nginx mulai merespons dengan HTTP status 429 Too Many Requests.
  2. **`conn.Do("PUBLISH

🏷️ Tags: #Nginx #High Concurrency #Performance #Sysadmin #Web Server