API Design & Integration: Cầu nối Giữa các Hệ thống

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ì:

  • Intuitive - developers hiểu ngay cách dùng
  • Consistent - patterns lặp lại, dễ đoán
  • Secure - bảo vệ data và prevent abuse
  • Scalable - handle được traffic tăng trưởng

Hãy cùng khám phá các chuẩn thiết kế API phổ biến và best practices.

Các Chuẩn Thiết kế API

RESTful API - The Industry Standard

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

  • GET: Retrieve data (read-only, safe, idempotent)
  • POST: Create new resource
  • PUT: Replace entire resource (idempotent)
  • PATCH: Partial update
  • DELETE: Remove resource (idempotent)

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 - Query Language cho APIs

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 - High Performance RPC

gRPC (Google Remote Procedure Call) dùng Protocol Buffers thay vì JSON, optimize cho performance.

Đặc điểm:

  • Binary protocol (nhanh hơn JSON)
  • HTTP/2 (multiplexing, streaming)
  • Strong typing với .proto files
  • Code generation cho multiple languages

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

Authentication & Authorization

Authentication - Xác thực "Bạn là ai?"

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:

  • Stateless (không cần database lookup)
  • Contains user info (reduce DB calls)
  • Works across services

Cons:

  • Khó revoke (vì stateless)
  • Token size lớn hơn session ID

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:

  • OAuth 2.0: Authorization (app access user's data)
  • OIDC: Authentication (verify user identity) + Authorization

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

Authorization - "Bạn được làm gì?"

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

API Design Principles

Idempotency

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

Stateless Design

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:

  • Dễ scale horizontally
  • Load balancer có thể route request đến bất kỳ server nào
  • Fault tolerance tốt hơn

API Versioning Strategies

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.

Rate Limiting

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

Key Takeaways

  • REST là standard cho web APIs - resource-based, stateless, HTTP verbs
  • GraphQL cho precise data fetching, gRPC cho high-performance internal communication
  • Authentication methods: Basic Auth, API Keys, OAuth 2.0, JWT, OpenID Connect
  • Authorization: RBAC cho simplicity, ABAC cho flexibility
  • Idempotency critical cho reliability - dùng idempotency keys cho POST
  • Stateless design enables horizontal scaling
  • VersioningRate Limiting quan trọng cho production APIs

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