system-design

API Gateway Pattern: Khi nào cần và cách implement đúng

API Gateway không phải cho mọi microservices. Đây là hướng dẫn thực tế khi nào cần, patterns phổ biến, và cách tránh single point of failure.

newspaper

Phạm Hoàng Long

5 tháng 1, 2026 schedule 7 phút đọc
API Gateway Pattern: Khi nào cần và cách implement đúng
Featured Image

Năm 2023, công ty tôi làm migrate từ monolith sang microservices.

Sau 3 tháng, chúng tôi có 15 services. Mỗi service tự implement:

  • Authentication
  • Rate limiting
  • Logging
  • CORS

Vấn đề: Duplicate code everywhere. Mỗi service có bug riêng. Nightmare để maintain.

Giải pháp? API Gateway.

Sau khi implement, development time giảm 40%. Bugs giảm 60%.

Đây là những gì tôi học được.

API Gateway là gì?

Definition: Single entry point cho tất cả client requests, route đến appropriate services.

Analogy: API Gateway giống receptionist ở khách sạn:

  • Client (guest) không biết room numbers (services)
  • Receptionist (gateway) route requests đến đúng room
  • Receptionist handle common tasks (check-in = auth, key card = token)

Architecture:

Client (Mobile/Web)

   API Gateway

   ┌───┴───┬───────┬────────┐
   ↓       ↓       ↓        ↓
User    Order   Payment  Inventory
Service Service Service  Service

Vấn đề API Gateway giải quyết

Problem 1: Cross-Cutting Concerns Duplication

Không có Gateway:

// user-service/middleware/auth.js
function authenticate(req, res, next) {
  const token = req.headers.authorization;
  if (!verifyToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

// order-service/middleware/auth.js
function authenticate(req, res, next) {
  const token = req.headers.authorization;
  if (!verifyToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

// payment-service/middleware/auth.js
// ... same code again

Duplicate code ở 15 services.

Có Gateway:

// api-gateway/middleware/auth.js
function authenticate(req, res, next) {
  const token = req.headers.authorization;
  if (!verifyToken(token)) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

// Tất cả services không cần auth middleware nữa

Single source of truth.

Problem 2: Client phải biết nhiều endpoints

Không có Gateway:

// Mobile app phải biết tất cả service URLs
const user = await fetch('https://user-service.com/api/users/123');
const orders = await fetch('https://order-service.com/api/orders?userId=123');
const payments = await fetch('https://payment-service.com/api/payments?userId=123');

Vấn đề:

  • Client coupling với services
  • Khó refactor (change service URLs)
  • Nhiều requests

Có Gateway:

// Mobile app chỉ biết 1 URL
const user = await fetch('https://api.example.com/users/123');
const orders = await fetch('https://api.example.com/orders?userId=123');
const payments = await fetch('https://api.example.com/payments?userId=123');

// Hoặc tốt hơn: Backend for Frontend (BFF)
const userData = await fetch('https://api.example.com/users/123/dashboard');
// Gateway aggregate data từ nhiều services

Problem 3: Protocol Translation

Scenario: Internal services dùng gRPC, client cần REST

Gateway solution:

Client (REST)

API Gateway (REST → gRPC)

Services (gRPC)

Patterns phổ biến

Pattern 1: Simple Proxy

Use case: Route requests, không modify

// api-gateway/routes.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// Route to services
app.use('/api/users', createProxyMiddleware({ 
  target: 'http://user-service:3001',
  changeOrigin: true 
}));

app.use('/api/orders', createProxyMiddleware({ 
  target: 'http://order-service:3002',
  changeOrigin: true 
}));

app.use('/api/payments', createProxyMiddleware({ 
  target: 'http://payment-service:3003',
  changeOrigin: true 
}));

Pros: Đơn giản Cons: Không có logic, chỉ route

Pattern 2: Backend for Frontend (BFF)

Use case: Aggregate data từ nhiều services

// api-gateway/routes/user-dashboard.js
app.get('/api/users/:id/dashboard', async (req, res) => {
  const userId = req.params.id;
  
  // Parallel requests to multiple services
  const [user, orders, payments] = await Promise.all([
    fetch(`http://user-service/users/${userId}`),
    fetch(`http://order-service/orders?userId=${userId}`),
    fetch(`http://payment-service/payments?userId=${userId}`)
  ]);
  
  // Aggregate response
  res.json({
    user: await user.json(),
    orders: await orders.json(),
    payments: await payments.json()
  });
});

Pros: Giảm số requests từ client Cons: Gateway phải biết business logic

Pattern 3: API Composition

Use case: Combine data từ nhiều services

app.get('/api/orders/:id/details', async (req, res) => {
  const orderId = req.params.id;
  
  // Get order
  const order = await fetch(`http://order-service/orders/${orderId}`).then(r => r.json());
  
  // Get user info
  const user = await fetch(`http://user-service/users/${order.userId}`).then(r => r.json());
  
  // Get product details
  const products = await Promise.all(
    order.items.map(item => 
      fetch(`http://product-service/products/${item.productId}`).then(r => r.json())
    )
  );
  
  // Compose response
  res.json({
    order: {
      ...order,
      user: {
        name: user.name,
        email: user.email
      },
      items: order.items.map((item, i) => ({
        ...item,
        product: products[i]
      }))
    }
  });
});

Pros: Rich responses Cons: Complex, có thể chậm

Implementation: Kong Gateway

Kong là API Gateway phổ biến nhất (open-source).

Setup

# Docker Compose
version: '3'
services:
  kong-database:
    image: postgres:13
    environment:
      POSTGRES_USER: kong
      POSTGRES_DB: kong
      POSTGRES_PASSWORD: kong
  
  kong:
    image: kong:latest
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
    ports:
      - "8000:8000"  # Proxy
      - "8001:8001"  # Admin API
    depends_on:
      - kong-database

Config Services

# Add user service
curl -i -X POST http://localhost:8001/services \
  --data name=user-service \
  --data url=http://user-service:3001

# Add route
curl -i -X POST http://localhost:8001/services/user-service/routes \
  --data paths[]=/api/users

Add Plugins

Authentication:

curl -i -X POST http://localhost:8001/services/user-service/plugins \
  --data name=jwt

Rate Limiting:

curl -i -X POST http://localhost:8001/services/user-service/plugins \
  --data name=rate-limiting \
  --data config.minute=100 \
  --data config.policy=local

CORS:

curl -i -X POST http://localhost:8001/services/user-service/plugins \
  --data name=cors \
  --data config.origins=* \
  --data config.methods=GET,POST,PUT,DELETE

Avoiding Single Point of Failure

Vấn đề: API Gateway down → Toàn bộ hệ thống down

Solution 1: Multiple Instances + Load Balancer

                Load Balancer

        ┌─────────────┼─────────────┐
        ↓             ↓             ↓
   Gateway 1     Gateway 2     Gateway 3
        ↓             ↓             ↓
              Services

Config:

# docker-compose.yml
services:
  gateway-1:
    image: kong:latest
    # ... config
  
  gateway-2:
    image: kong:latest
    # ... config
  
  gateway-3:
    image: kong:latest
    # ... config
  
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
# nginx.conf
upstream gateways {
  server gateway-1:8000;
  server gateway-2:8000;
  server gateway-3:8000;
}

server {
  listen 80;
  location / {
    proxy_pass http://gateways;
  }
}

Solution 2: Health Checks

// Gateway health check endpoint
app.get('/health', async (req, res) => {
  try {
    // Check database connection
    await db.ping();
    
    // Check critical services
    await Promise.all([
      fetch('http://user-service/health'),
      fetch('http://order-service/health')
    ]);
    
    res.json({ status: 'healthy' });
  } catch (error) {
    res.status(503).json({ status: 'unhealthy', error: error.message });
  }
});

Load balancer config:

upstream gateways {
  server gateway-1:8000 max_fails=3 fail_timeout=30s;
  server gateway-2:8000 max_fails=3 fail_timeout=30s;
  server gateway-3:8000 max_fails=3 fail_timeout=30s;
}

# Health check
location /health {
  proxy_pass http://gateways/health;
  proxy_next_upstream error timeout http_503;
}

Solution 3: Circuit Breaker

const CircuitBreaker = require('opossum');

const options = {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
};

const breaker = new CircuitBreaker(async (serviceUrl) => {
  return await fetch(serviceUrl);
}, options);

breaker.fallback(() => {
  return { error: 'Service temporarily unavailable' };
});

app.get('/api/users/:id', async (req, res) => {
  try {
    const response = await breaker.fire(`http://user-service/users/${req.params.id}`);
    res.json(response);
  } catch (error) {
    res.status(503).json({ error: 'Service unavailable' });
  }
});

Performance Optimization

1. Caching

const redis = require('redis');
const client = redis.createClient();

app.get('/api/users/:id', async (req, res) => {
  const userId = req.params.id;
  const cacheKey = `user:${userId}`;
  
  // Check cache
  const cached = await client.get(cacheKey);
  if (cached) {
    return res.json(JSON.parse(cached));
  }
  
  // Fetch from service
  const user = await fetch(`http://user-service/users/${userId}`).then(r => r.json());
  
  // Cache for 5 minutes
  await client.setex(cacheKey, 300, JSON.stringify(user));
  
  res.json(user);
});

2. Request Batching

const DataLoader = require('dataloader');

const userLoader = new DataLoader(async (userIds) => {
  const response = await fetch(`http://user-service/users?ids=${userIds.join(',')}`);
  const users = await response.json();
  return userIds.map(id => users.find(u => u.id === id));
});

app.get('/api/orders/:id', async (req, res) => {
  const order = await fetch(`http://order-service/orders/${req.params.id}`).then(r => r.json());
  
  // Batch user requests
  const users = await Promise.all(
    order.items.map(item => userLoader.load(item.userId))
  );
  
  res.json({ order, users });
});

Khi nào NÊN dùng API Gateway?

Dùng khi:

  • Microservices architecture (> 5 services)
  • Cần centralize cross-cutting concerns
  • Multiple client types (web, mobile, IoT)
  • Public API

Không dùng khi:

  • Monolith application
  • < 3 services
  • Internal-only services
  • Extreme low latency requirements

Kết luận

API Gateway là pattern mạnh mẽ cho microservices.

Pros:

  • Centralize cross-cutting concerns
  • Simplify client code
  • Protocol translation
  • API composition

Cons:

  • Single point of failure (nếu không setup HA)
  • Added latency
  • Complexity

Lời khuyên:

  • Start simple (proxy pattern)
  • Add features khi cần (auth, rate limiting)
  • Setup HA từ đầu
  • Monitor performance
  • Use managed services nếu có thể (AWS API Gateway, Google Cloud Endpoints)

Tools:

  • Kong (open-source, feature-rich)
  • Nginx (simple, fast)
  • Traefik (cloud-native)
  • AWS API Gateway (managed)

API Gateway không phải silver bullet. Nhưng nếu dùng đúng, nó sẽ simplify architecture đáng kể.

quizQuick Quiz
Câu 1/3

Vấn đề chính mà API Gateway giải quyết là gì?

history_edu Góc học tập & giải trí

Thử Thách Kiến Thức Lịch Sử?

Khám phá hàng trăm câu hỏi trắc nghiệm lịch sử thú vị tại HistoQuiz. Vừa học vừa chơi, nâng cao kiến thức ngay hôm nay!

Chơi Ngay arrow_forward