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.
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ể.
Vấn đề chính mà API Gateway giải quyết là gì?
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!