API Gateway Patterns — System Design

API Gateway bukan hanya sekedar reverse proxy — ada banyak pola canggih yang bisa diimplementasikan untuk membuat sistem lebih robust dan efisien. Pola 1: Backe

API Gateway bukan hanya sekedar reverse proxy — ada banyak pola canggih yang bisa diimplementasikan untuk membuat sistem lebih robust dan efisien.

Pola 1: Backend for Frontend (BFF)

Buat gateway khusus per jenis client, bukan satu gateway untuk semua:

Mobile App   → BFF Mobile   → Microservices
Web App      → BFF Web      → (sama)
Partner API  → BFF Partner  → (sama)
// BFF Mobile — response lebih ringkas untuk bandwidth hemat
app.get("/api/mobile/products", async (req, res) => {
  const products = await productService.getAll();
  // Kirim hanya field yang dibutuhkan mobile
  res.json(products.map(p => ({
    id: p.id,
    name: p.name,
    price: p.price,
    thumb: p.thumbnail_url  // bukan full image
  })));
});

// BFF Web — response lebih lengkap
app.get("/api/web/products", async (req, res) => {
  const [products, categories, featured] = await Promise.all([
    productService.getAll(),
    categoryService.getAll(),
    productService.getFeatured()
  ]);
  res.json({ products, categories, featured });
});

Pola 2: Circuit Breaker

Cegah cascade failure — jika service down, jangan terus coba panggil:

Status: CLOSED → Semua request diteruskan normal
Status: OPEN   → Semua request langsung gagal (tidak panggil service)
Status: HALF-OPEN → Coba 1 request, jika berhasil → CLOSED lagi

Transisi:
CLOSED  → (error >= threshold) → OPEN
OPEN    → (timeout berlalu)    → HALF-OPEN
HALF-OPEN → (berhasil)         → CLOSED
HALF-OPEN → (gagal)            → OPEN
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async call(fn) {
    if (this.state === "OPEN") {
      if (Date.now() < this.nextAttempt) {
        throw new Error("Circuit breaker OPEN — service tidak tersedia");
      }
      this.state = "HALF_OPEN";
    }
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (err) {
      this.onFailure();
      throw err;
    }
  }

  onSuccess() { this.failureCount = 0; this.state = "CLOSED"; }
  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.threshold) {
      this.state = "OPEN";
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
}

Pola 3: Request Aggregation (GraphQL-style)

Gateway menggabungkan data dari beberapa service dalam satu call:

// Tanpa aggregation: client butuh 3 request
// GET /users/42
// GET /orders?userId=42
// GET /recommendations?userId=42

// Dengan aggregation: satu request
app.get("/api/profile/:id", async (req, res) => {
  const [user, orders, recs] = await Promise.all([
    userService.get(req.params.id),
    orderService.getByUser(req.params.id),
    recService.getForUser(req.params.id),
  ]);
  res.json({ user, orders, recommendations: recs });
});

Pola 4: API Versioning

/api/v1/users → Legacy format
/api/v2/users → New format dengan field baru
/api/v3/users → Breaking change, field dihapus
// Gateway bisa translate v1 request ke internal format
app.get("/api/v1/users/:id", async (req, res) => {
  const user = await userService.get(req.params.id);
  // v1 masih pakai snake_case
  res.json({
    user_id: user.id,
    full_name: user.name,
    email_address: user.email
  });
});

app.get("/api/v2/users/:id", async (req, res) => {
  const user = await userService.get(req.params.id);
  // v2 pakai camelCase
  res.json({ userId: user.id, fullName: user.name, email: user.email });
});

Pola 5: Retry dengan Exponential Backoff

async function callWithRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxRetries) throw err;
      // Exponential backoff: 1s, 2s, 4s, 8s...
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Summary pola API Gateway:

Pola Masalah yang Diselesaikan
BFF Kebutuhan client yang berbeda-beda
Circuit Breaker Cascade failure saat service down
Aggregation Terlalu banyak round-trip dari client
Versioning Perubahan API tanpa breaking client lama
Retry + Backoff Kegagalan sementara yang bisa di-recover

🎭 Analogi sehari-hari: Tukang listrik handle MCB di rumah. Korslet di kamar mandi → MCB jeglek (circuit breaker), nyala lain tetap aman. Lampu redup karena tegangan turun → coba nyalain ulang (retry). Beda kebutuhan kamar (dapur butuh daya besar, kamar tidur kecil) → split panel (BFF).

💡 Circuit Breaker rule: Kalau service B mati, Service A jangan terus call B berkali-kali. State circuit breaker:

⚠️ Jebakan klasik:

🎯 Pattern stack untuk resilience:

  1. Timeout wajib (jangan tunggu selamanya)
  2. Retry + exponential backoff untuk transient failure
  3. Circuit breaker untuk persistent failure
  4. Bulkhead isolation pool antar service
  5. Fallback response saat semua gagal

TL;DR: API Gateway pattern = BFF (per-client), Circuit Breaker (fail fast), Aggregation (combine calls), Versioning (backwards compat), Retry+Backoff (transient recovery). Stack pattern bersama = resilience mature.