API (Application Programming Interface) là "hợp đồng" giữa các services - định nghĩa cách chúng giao tiếp với nhau. Trong thời đại microservices và cloud computing, khả năng thiết kế API tốt là kỹ năng không thể thiếu của mọi engineer.
Một API thiết kế tốt thì:
Hãy cùng khám phá các chuẩn thiết kế API phổ biến và best practices.
REST (Representational State Transfer) là architectural style phổ biến nhất cho web APIs.
Nguyên tắc cốt lõi của REST:
1. Resource-based
Mọi thứ là "resource" với unique URI.
GET /users # Danh sách users
GET /users/123 # User cụ thể
POST /users # Tạo user mới
PUT /users/123 # Update toàn bộ user
PATCH /users/123 # Update một phần
DELETE /users/123 # Xóa user
2. Stateless
Mỗi request chứa đầy đủ thông tin cần thiết. Server không lưu session state.
# ❌ BAD: Dựa vào session
GET /next-page
# ✅ GOOD: Request chứa đầy đủ thông tin
GET /users?page=2&limit=20
3. HTTP Methods làm verbs
4. HTTP Status Codes
Response phải dùng đúng status codes:
2xx Success:
200 OK - Request thành công
201 Created - Resource được tạo
204 No Content - Success nhưng không return data
3xx Redirection:
301 Moved Permanently
304 Not Modified - Dùng cached version
4xx Client Errors:
400 Bad Request - Invalid syntax
401 Unauthorized - Authentication required
403 Forbidden - Authenticated nhưng không có permission
404 Not Found
409 Conflict - Duplicate resource
422 Unprocessable Entity - Validation failed
5xx Server Errors:
500 Internal Server Error
502 Bad Gateway
503 Service Unavailable
RESTful API Best Practices:
✅ Use nouns, not verbs in URIs
❌ GET /getUsers
❌ POST /createUser
✅ GET /users
✅ POST /users
✅ Use plural nouns
❌ GET /user/123
✅ GET /users/123
✅ Nest resources for relationships
GET /users/123/orders # Orders của user 123
GET /users/123/orders/456 # Order 456 của user 123
POST /users/123/orders # Tạo order cho user 123
✅ Use query parameters for filtering/sorting
GET /products?category=electronics&price_min=100&price_max=500
GET /users?sort=created_at&order=desc&limit=50
✅ Version your API
# Option 1: URI versioning
GET /v1/users
GET /v2/users
# Option 2: Header versioning
GET /users
Headers: Accept: application/vnd.api.v2+json
Ví dụ hoàn chỉnh - E-commerce API:
# Get products với filters
GET /v1/products?category=laptops&brand=apple&sort=price&order=asc&page=1&limit=20
Response: 200 OK
{
"data": [
{
"id": "prod_123",
"name": "MacBook Pro 16",
"price": 2499,
"category": "laptops",
"brand": "apple",
"in_stock": true
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45
}
}
# Create order
POST /v1/orders
Headers:
Authorization: Bearer eyJhbGc...
Content-Type: application/json
Body:
{
"items": [
{"product_id": "prod_123", "quantity": 1}
],
"shipping_address": {...}
}
Response: 201 Created
{
"id": "order_789",
"status": "pending",
"total": 2499,
"created_at": "2024-02-01T10:00:00Z"
}
GraphQL là alternative cho REST, cho phép client request chính xác data cần thiết.
Vấn đề của REST:
Over-fetching: Client nhận nhiều data hơn cần
GET /users/123
Response: {
"id": 123,
"name": "John",
"email": "john@example.com",
"address": {...}, # Không cần
"preferences": {...}, # Không cần
"orders": [...] # Không cần
}
Under-fetching: Client phải gọi nhiều endpoints
# Để hiển thị user profile với orders:
GET /users/123 # Request 1
GET /users/123/orders # Request 2
GET /orders/456/items # Request 3 (cho mỗi order)
GraphQL giải quyết:
# Single request, precise data
query {
user(id: 123) {
name
email
orders {
id
total
items {
product {
name
price
}
quantity
}
}
}
}
# Response chỉ có data được request
{
"data": {
"user": {
"name": "John",
"email": "john@example.com",
"orders": [
{
"id": "order_789",
"total": 2499,
"items": [...]
}
]
}
}
}
GraphQL Mutations (for writes):
mutation {
createOrder(input: {
items: [{productId: "prod_123", quantity: 1}]
shippingAddress: {...}
}) {
id
status
total
}
}
Khi nào dùng GraphQL:
✅ Mobile apps (tiết kiệm bandwidth)
✅ Complex data requirements với nhiều relationships
✅ Frontend cần flexibility trong queries
✅ Microservices architecture (GraphQL làm API gateway)
Khi nào dùng REST:
✅ Simple CRUD operations
✅ Caching quan trọng (REST dễ cache hơn)
✅ File uploads/downloads
✅ Team chưa familiar với GraphQL
gRPC (Google Remote Procedure Call) dùng Protocol Buffers thay vì JSON, optimize cho performance.
Đặc điểm:
Ví dụ Protocol Buffer definition:
// user.proto
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int32 id = 1;
}
Khi nào dùng gRPC:
✅ Microservices communication (internal)
✅ Real-time streaming
✅ High-performance requirements
✅ Polyglot environments (nhiều languages)
Không dùng gRPC khi:
❌ Browser clients (limited support)
❌ Human-readable responses cần thiết
❌ REST ecosystem và tooling đã established
1. Basic Authentication
Username và password encoded trong header.
GET /api/users
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
# Base64("username:password")
Pros: Đơn giản
Cons: Không secure nếu không dùng HTTPS, credentials gửi mỗi request
Use case: Internal tools, development environments
2. API Keys
Static token cho mỗi client.
GET /api/users
X-API-Key: sk_live_abc123xyz789
Pros: Đơn giản, dễ implement
Cons: Không expiry, khó revoke, không phân biệt users
Use case: Third-party integrations, server-to-server
3. OAuth 2.0
Industry standard cho delegated authorization.
OAuth Flow (Authorization Code):
1. User → Frontend → "Login with Google"
2. Frontend → Google → Redirect với client_id
3. Google → User → Login form
4. Google → Frontend → Authorization code
5. Frontend → Backend → Exchange code for token
6. Backend → Google → POST /token với code + client_secret
7. Google → Backend → Access token + Refresh token
8. Backend → Frontend → Token
9. Frontend → API → Requests với Bearer token
Access Token Example:
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Pros: Secure, delegated access, fine-grained scopes
Cons: Complex to implement
Use case: Social login, third-party app access
4. JWT (JSON Web Tokens)
Self-contained tokens chứa claims.
Header.Payload.Signature
# Decoded:
{
"header": {"alg": "HS256", "typ": "JWT"},
"payload": {
"sub": "user123",
"name": "John Doe",
"role": "admin",
"exp": 1735689600
},
"signature": "..."
}
Pros:
Cons:
Best practices:
# Short expiry cho access token
access_token_expiry = 15 minutes
# Long expiry cho refresh token
refresh_token_expiry = 7 days
# Rotate refresh tokens
# Store refresh tokens in DB để có thể revoke
5. OpenID Connect (OIDC)
Layer trên OAuth 2.0, thêm identity information.
Difference:
ID Token (JWT):
{
"iss": "https://accounts.google.com",
"sub": "user123",
"email": "john@example.com",
"email_verified": true,
"name": "John Doe",
"picture": "https://..."
}
Use case: Single Sign-On (SSO), social login
1. Role-Based Access Control (RBAC)
Permissions gán cho roles.
roles = {
"admin": ["read", "write", "delete"],
"editor": ["read", "write"],
"viewer": ["read"]
}
user = {"id": 123, "role": "editor"}
def check_permission(user, action):
return action in roles[user["role"]]
# Usage
if check_permission(user, "delete"):
delete_resource()
else:
return 403 # Forbidden
Pros: Đơn giản, dễ quản lý
Cons: Không flexible cho complex permissions
2. Attribute-Based Access Control (ABAC)
Decisions dựa trên attributes của user, resource, và environment.
def can_access(user, resource, action):
# User attributes
if user["department"] == "sales" and \
resource["type"] == "customer_data" and \
action == "read" and \
user["clearance_level"] >= 2:
return True
# Time-based
if action == "write" and \
not is_business_hours():
return False
return False
Pros: Flexible, fine-grained
Cons: Complex to implement và debug
Operation có thể gọi nhiều lần mà kết quả giống như gọi 1 lần.
Tại sao quan trọng: Network unreliable - request có thể timeout và retry.
# Idempotent - safe to retry
GET /users/123 # Multiple calls → same result
PUT /users/123 {...} # Replace resource → same final state
DELETE /users/123 # Delete once = delete multiple times
# NOT idempotent
POST /orders # Multiple calls → multiple orders!
Giải pháp cho POST:
# Option 1: Idempotency key
POST /orders
Headers: Idempotency-Key: uuid-123-456
# Server checks: đã process uuid-123-456 chưa?
# Nếu rồi → return previous response
# Nếu chưa → process và lưu result với key
# Server-side implementation
def create_order(data, idempotency_key):
# Check cache/DB
existing = get_cached_response(idempotency_key)
if existing:
return existing
# Process
order = process_order(data)
# Cache result
cache_response(idempotency_key, order, ttl=86400)
return order
Server không lưu client state giữa các requests.
❌ Stateful (BAD):
# Server lưu user's cart in memory
sessions = {}
@app.post("/cart/add")
def add_to_cart(item):
session_id = request.headers["Session-ID"]
if session_id not in sessions:
sessions[session_id] = []
sessions[session_id].append(item)
# Problem: Không scale (mỗi server có sessions khác nhau)
✅ Stateless (GOOD):
@app.post("/cart/add")
def add_to_cart(item):
# Client gửi kèm token
user_id = decode_jwt(request.headers["Authorization"])
# State lưu trong DB (shared across servers)
db.carts.insert({"user_id": user_id, "item": item})
Lợi ích:
1. URI Versioning
/v1/users
/v2/users
Pros: Rõ ràng, dễ route
Cons: Ugly URLs, cache invalidation
2. Header Versioning
GET /users
Accept: application/vnd.api.v2+json
Pros: Clean URLs
Cons: Khó test (phải set headers)
3. Query Parameter
/users?version=2
Pros: Đơn giản
Cons: Không RESTful
Recommendation: URI versioning cho major changes, header cho minor.
Prevent abuse và ensure fair usage.
Algorithms:
1. Fixed Window
# Allow 100 requests/hour
window = datetime.now().hour
count = redis.get(f"rate:{user_id}:{window}") or 0
if count >= 100:
return 429 # Too Many Requests
redis.incr(f"rate:{user_id}:{window}")
redis.expire(f"rate:{user_id}:{window}", 3600)
Problem: Burst at window boundaries (100 req at 10:59, 100 req at 11:00 → 200 req/min!)
2. Sliding Window
Track requests trong rolling window (last 60 minutes).
3. Token Bucket
Refill tokens at fixed rate, consume per request.
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity
self.tokens = capacity
self.refill_rate = refill_rate # tokens/second
def consume(self, tokens=1):
self.refill()
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
def refill(self):
now = time.time()
elapsed = now - self.last_refill
self.tokens = min(self.capacity,
self.tokens + elapsed * self.refill_rate)
self.last_refill = now
Rate limit headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1735689600
Trong bài tiếp theo, chúng ta sẽ tìm hiểu Code Quality & Refactoring - cách viết clean code, nhận diện code smells, và refactoring techniques.
Bài viết thuộc series "From Zero to AI Engineer" - Module 2: Design & Architecture