Skip to content

🔌 API DESIGN

REST, GraphQL, gRPC - Chọn đúng Paradigm, Thiết kế đúng Pattern


🎯 Mục tiêu Học tập

Sau khi hoàn thành module này, bạn sẽ:

  1. So sánh và chọn đúng giữa REST, GraphQL, gRPC
  2. Thiết kế API Versioning strategy phù hợp
  3. Implement Pagination hiệu quả (cursor vs offset)
  4. Áp dụng Idempotency cho retry-safe operations
  5. Xây dựng Error Model chuẩn RFC 7807
  6. Triển khai Rate LimitingAuth patterns
  7. Đảm bảo Backward Compatibility khi evolve schema
  8. Tích hợp Observability Hooks cho production

📋 Prerequisites

📚 Kiến thức cần có


1. API Paradigms: REST vs GraphQL vs gRPC

1.1 REST: The Universal Standard

NOTE

🎓 Giáo sư Tom: REST (Representational State Transfer) được Roy Fielding định nghĩa năm 2000. Đây không phải là protocol mà là architectural style với 6 constraints. Hầu hết "REST APIs" ngày nay thực ra là "HTTP APIs" - không hoàn toàn tuân thủ REST constraints.

┌─────────────────────────────────────────────────────────────────────────┐
│                         REST ARCHITECTURE                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   CLIENT                              SERVER                             │
│   ┌─────────────────┐                 ┌─────────────────────────────┐   │
│   │                 │   HTTP Request  │                             │   │
│   │  GET /users/123 │────────────────►│  Resource: /users/{id}      │   │
│   │  Accept: json   │                 │                             │   │
│   │                 │◄────────────────│  { "id": 123, "name": ... } │   │
│   │                 │   HTTP Response │                             │   │
│   └─────────────────┘                 └─────────────────────────────┘   │
│                                                                          │
│   REST Constraints:                                                      │
│   ✅ Stateless - Mỗi request chứa đủ thông tin                          │
│   ✅ Uniform Interface - Chuẩn hóa resource representation              │
│   ✅ Cacheable - Response có thể cache                                  │
│   ✅ Client-Server - Tách biệt concerns                                 │
│   ✅ Layered System - Có thể thêm proxy, gateway                        │
│   ⚠️ Code on Demand (optional) - Server gửi executable code            │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

HTTP Methods Cheat Sheet:

MethodIdempotentSafeUse Case
GET✅ Yes✅ YesĐọc resource
POST❌ No❌ NoTạo resource mới
PUT✅ Yes❌ NoReplace toàn bộ resource
PATCH❌ No*❌ NoPartial update
DELETE✅ Yes❌ NoXóa resource

*PATCH có thể idempotent nếu thiết kế đúng cách

1.2 GraphQL: The Flexible Query Language

TIP

🔧 Kỹ sư Raizo: GraphQL giải quyết 2 vấn đề lớn của REST: Over-fetching (lấy nhiều hơn cần) và Under-fetching (phải gọi nhiều endpoints). Nhưng đổi lại, bạn phải handle N+1 query problemcomplexity budgeting.

graphql
# GraphQL Query - Client chọn chính xác fields cần
query GetUserWithOrders {
  user(id: "123") {
    id
    name
    email
    orders(first: 5) {
      id
      total
      items {
        productName
        quantity
      }
    }
  }
}

# Response - Chỉ những gì client yêu cầu
{
  "data": {
    "user": {
      "id": "123",
      "name": "John Doe",
      "email": "john@example.com",
      "orders": [
        {
          "id": "ord-001",
          "total": 150.00,
          "items": [...]
        }
      ]
    }
  }
}

GraphQL Trade-offs:

Ưu điểmNhược điểm
Flexible queriesN+1 query problem
Single endpointComplexity budgeting
Strong typingCaching khó hơn REST
Self-documentingLearning curve

1.3 gRPC: The Performance Champion

┌─────────────────────────────────────────────────────────────────────────┐
│                           gRPC ARCHITECTURE                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   CLIENT                                    SERVER                       │
│   ┌─────────────────┐                       ┌─────────────────────┐     │
│   │ Generated Stub  │   HTTP/2 + Protobuf   │ Service Implementation│   │
│   │                 │◄─────────────────────►│                     │     │
│   │ userClient.     │   Binary, Fast        │ func GetUser(...) { │     │
│   │   GetUser(req)  │   Multiplexed         │   return &User{...} │     │
│   └─────────────────┘                       │ }                   │     │
│                                             └─────────────────────┘     │
│                                                                          │
│   Streaming Modes:                                                       │
│   ├── Unary: Request → Response (như REST)                              │
│   ├── Server Streaming: Request → Stream of Responses                   │
│   ├── Client Streaming: Stream of Requests → Response                   │
│   └── Bidirectional: Stream ↔ Stream (real-time)                        │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
protobuf
// user.proto - Protocol Buffer definition
syntax = "proto3";

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (stream User);  // Server streaming
}

message GetUserRequest {
  string user_id = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int64 created_at = 4;
}

1.4 Decision Table: Khi nào dùng gì?

CriteriaRESTGraphQLgRPC
Best forPublic APIs, CRUDMobile apps, complex queriesMicroservices, real-time
Performance⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Flexibility⭐⭐⭐⭐⭐⭐⭐⭐⭐
Caching⭐⭐⭐⭐⭐⭐⭐⭐⭐
Browser Support⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ (cần proxy)
Learning Curve⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

2. API Versioning Strategies

IMPORTANT

Versioning là contract với consumers. Một khi publish API version, bạn không được breaking change mà không warning.

2.1 Versioning Approaches

┌─────────────────────────────────────────────────────────────────────────┐
│                     API VERSIONING STRATEGIES                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1️⃣ URL PATH VERSIONING (Recommended for most cases)                    │
│  ──────────────────────────────────────────────────────────────────────  │
│  GET /v1/users/123                                                       │
│  GET /v2/users/123                                                       │
│                                                                          │
│  ✅ Explicit, visible                                                    │
│  ✅ Easy to route (nginx, API Gateway)                                   │
│  ❌ Violates REST "resource should have one URI"                        │
│                                                                          │
│  2️⃣ HEADER VERSIONING                                                   │
│  ──────────────────────────────────────────────────────────────────────  │
│  GET /users/123                                                          │
│  Accept: application/vnd.company.v2+json                                 │
│                                                                          │
│  ✅ Clean URLs                                                           │
│  ✅ Pure REST                                                            │
│  ❌ Hidden, harder to test (curl, browser)                              │
│                                                                          │
│  3️⃣ QUERY PARAMETER VERSIONING                                          │
│  ──────────────────────────────────────────────────────────────────────  │
│  GET /users/123?version=2                                                │
│                                                                          │
│  ✅ Easy to test                                                         │
│  ❌ Optional params can be forgotten                                    │
│  ❌ Caching issues (same URL, different responses)                      │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

2.2 Semantic Versioning for APIs

MAJOR.MINOR.PATCH
  │     │     └── Bug fixes (backward compatible)
  │     └──────── New features (backward compatible)
  └────────────── Breaking changes

Breaking Changes (cần bump MAJOR):

Change TypeBreaking?Example
Remove field✅ Yesuser.age removed
Rename field✅ Yesuser.nameuser.full_name
Change field type✅ Yesid: numberid: string
Add required field (request)✅ YesNew required param
Add optional field❌ NoNew optional param
Add new endpoint❌ NoGET /v1/analytics

TIP

🔧 Kỹ sư Raizo: Rule of thumb: Bump MAJOR version khi bất kỳ existing client nào sẽ break nếu không update code. Với internal APIs, bạn có thể aggressive hơn. Với public APIs, support ít nhất 2 versions song song và announce deprecation 6-12 tháng trước.


3. Pagination Patterns

3.1 Offset-based Pagination

typescript
// Request
GET /users?limit=20&offset=40

// Response
{
  "data": [...],
  "pagination": {
    "limit": 20,
    "offset": 40,
    "total": 1500
  }
}
┌─────────────────────────────────────────────────────────────────────────┐
│                     OFFSET PAGINATION PROBLEM                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Page 1 (offset=0):   [A] [B] [C] [D] [E]                               │
│                                                                          │
│  ───── User đang xem Page 1, meanwhile có insert mới ─────              │
│                                                                          │
│  Data sau insert [X]: [X] [A] [B] [C] [D] [E]                           │
│                                                                          │
│  Page 2 (offset=5):   [E] [F] [G] [H] [I]  ← Item E bị duplicate!       │
│                                                                          │
│  ⚠️ Vấn đề:                                                             │
│  • Insert/Delete giữa requests → missing hoặc duplicate items           │
│  • OFFSET lớn → performance chậm (DB scan từ đầu)                       │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

3.2 Cursor-based Pagination (Keyset)

typescript
// Request - dùng cursor thay vì offset
GET /users?limit=20&cursor=user_123

// Response
{
  "data": [...],
  "pagination": {
    "next_cursor": "user_143",
    "has_more": true
  }
}
sql
-- Cursor-based query (efficient)
SELECT * FROM users 
WHERE id > 'user_123'  -- Cursor là last seen ID
ORDER BY id ASC
LIMIT 20;

-- So với Offset (inefficient với offset lớn)
SELECT * FROM users 
ORDER BY id ASC
LIMIT 20 OFFSET 10000;  -- Phải scan 10000 rows đầu tiên

3.3 Pagination Decision Table

FactorOffsetCursor
Jump to page N✅ Easy❌ Not possible
Consistent results❌ Drift possible✅ Stable
Performance (large dataset)❌ O(offset)✅ O(1)
Implementation⭐⭐⭐⭐⭐ Simple⭐⭐⭐ Complex
Infinite scroll⭐⭐⭐⭐⭐⭐⭐
Total count✅ Easy❌ Expensive

WARNING

Khi nào dùng Cursor?

  • Dataset > 10,000 items
  • Real-time data (feeds, messages)
  • Infinite scroll UI
  • Performance-critical applications

4. Idempotency Design

CAUTION

🎓 Giáo sư Tom: Idempotency là property mà một operation có thể thực hiện nhiều lần nhưng kết quả giống như thực hiện 1 lần. Trong distributed systems với network failures, idempotency là BẮT BUỘC cho reliability.

4.1 Idempotency Key Pattern

┌─────────────────────────────────────────────────────────────────────────┐
│                      IDEMPOTENCY KEY FLOW                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Client                            Server                                │
│    │                                 │                                   │
│    │  POST /payments                 │                                   │
│    │  Idempotency-Key: uuid-abc-123  │                                   │
│    │  { amount: 100 }                │                                   │
│    │────────────────────────────────►│                                   │
│    │                                 │                                   │
│    │                        ┌────────┴────────┐                          │
│    │                        │ Check key exists │                          │
│    │                        │ in idempotency   │                          │
│    │                        │ store?           │                          │
│    │                        └────────┬────────┘                          │
│    │                                 │                                   │
│    │              ┌──────────────────┴──────────────────┐               │
│    │              │                                      │               │
│    │         [Key NOT found]                       [Key FOUND]          │
│    │              │                                      │               │
│    │              ▼                                      ▼               │
│    │      Process payment                    Return cached response     │
│    │      Store result with key              (no re-processing)         │
│    │              │                                      │               │
│    │              └──────────────────┬──────────────────┘               │
│    │                                 │                                   │
│    │◄────────────────────────────────│                                   │
│    │    201 Created / 200 OK         │                                   │
│    │    (same response both times)   │                                   │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

4.2 Implementation Example

typescript
// Server-side idempotency handler
async function processPayment(req: Request): Promise<Response> {
  const idempotencyKey = req.headers['Idempotency-Key'];
  
  if (!idempotencyKey) {
    return { status: 400, error: 'Idempotency-Key header required' };
  }

  // 1. Check if we've seen this key before
  const cached = await idempotencyStore.get(idempotencyKey);
  if (cached) {
    // Return cached response (no re-processing)
    return cached.response;
  }

  // 2. Process the payment
  const result = await paymentService.charge(req.body);

  // 3. Store result with idempotency key (TTL: 24h)
  await idempotencyStore.set(idempotencyKey, {
    response: result,
    processedAt: Date.now()
  }, { ttl: 86400 });

  return result;
}

4.3 Idempotency by HTTP Method

MethodNaturally Idempotent?Strategy
GET✅ YesNo action needed
PUT✅ YesReplace entire resource
DELETE✅ YesDelete if exists, no-op if not
POST❌ NoUse Idempotency-Key header
PATCH❌ NoUse conditional update (ETag)

5. Error Model (RFC 7807)

5.1 Problem Details Standard

┌─────────────────────────────────────────────────────────────────────────┐
│                    RFC 7807 - PROBLEM DETAILS                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Standard Fields:                                                        │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  {                                                                 │  │
│  │    "type": "https://api.example.com/errors/insufficient-funds",   │  │
│  │    "title": "Insufficient Funds",                                  │  │
│  │    "status": 400,                                                  │  │
│  │    "detail": "Account balance $50 is less than transfer $100",    │  │
│  │    "instance": "/transfers/abc-123",                               │  │
│  │                                                                    │  │
│  │    // Extension fields (custom)                                    │  │
│  │    "balance": 50.00,                                               │  │
│  │    "required": 100.00,                                             │  │
│  │    "traceId": "trace-xyz-789"                                      │  │
│  │  }                                                                 │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                                                          │
│  Content-Type: application/problem+json                                  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

5.2 Error Taxonomy

typescript
// Error codes taxonomy
const ErrorCodes = {
  // 4xx Client Errors
  VALIDATION: {
    INVALID_INPUT: 'ERR_VALIDATION_001',
    MISSING_FIELD: 'ERR_VALIDATION_002',
    INVALID_FORMAT: 'ERR_VALIDATION_003',
  },
  AUTH: {
    UNAUTHORIZED: 'ERR_AUTH_001',
    TOKEN_EXPIRED: 'ERR_AUTH_002',
    INSUFFICIENT_PERMISSIONS: 'ERR_AUTH_003',
  },
  RESOURCE: {
    NOT_FOUND: 'ERR_RESOURCE_001',
    ALREADY_EXISTS: 'ERR_RESOURCE_002',
    CONFLICT: 'ERR_RESOURCE_003',
  },
  RATE_LIMIT: {
    TOO_MANY_REQUESTS: 'ERR_RATE_001',
    QUOTA_EXCEEDED: 'ERR_RATE_002',
  },

  // 5xx Server Errors
  INTERNAL: {
    UNEXPECTED: 'ERR_INTERNAL_001',
    SERVICE_UNAVAILABLE: 'ERR_INTERNAL_002',
    DEPENDENCY_FAILED: 'ERR_INTERNAL_003',
  }
};

5.3 Error Response Examples

json
// 400 Bad Request - Validation Error
{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Failed",
  "status": 400,
  "detail": "Request body contains invalid fields",
  "errors": [
    {
      "field": "email",
      "code": "ERR_VALIDATION_003",
      "message": "Invalid email format"
    },
    {
      "field": "age",
      "code": "ERR_VALIDATION_001",
      "message": "Age must be a positive integer"
    }
  ],
  "traceId": "trace-abc-123"
}

// 429 Too Many Requests
{
  "type": "https://api.example.com/errors/rate-limit",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded the rate limit of 100 requests per minute",
  "retryAfter": 45,
  "limit": 100,
  "remaining": 0,
  "resetAt": "2024-01-15T10:30:00Z"
}

6. Authentication & Authorization

6.1 Auth Methods Comparison

┌─────────────────────────────────────────────────────────────────────────┐
│                      AUTHENTICATION METHODS                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  1️⃣ API KEYS                                                            │
│  ──────────────────────────────────────────────────────────────────────  │
│  Authorization: ApiKey sk_live_abc123                                    │
│                                                                          │
│  ✅ Simple implementation                                                │
│  ✅ Good for server-to-server                                            │
│  ❌ No user context                                                      │
│  ❌ Rotation phức tạp                                                    │
│                                                                          │
│  2️⃣ JWT (JSON Web Token)                                                │
│  ──────────────────────────────────────────────────────────────────────  │
│  Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVC...            │
│                                                                          │
│  ✅ Stateless (no DB lookup)                                             │
│  ✅ Contains user claims                                                 │
│  ❌ Cannot revoke (until expiry)                                        │
│  ❌ Token size lớn                                                       │
│                                                                          │
│  3️⃣ OAuth 2.0                                                           │
│  ──────────────────────────────────────────────────────────────────────  │
│  Authorization: Bearer <access_token>                                    │
│                                                                          │
│  ✅ Delegated authorization                                              │
│  ✅ Scoped access                                                        │
│  ✅ Token refresh                                                        │
│  ❌ Complex implementation                                               │
│                                                                          │
│  4️⃣ mTLS (Mutual TLS)                                                   │
│  ──────────────────────────────────────────────────────────────────────  │
│  Client presents X.509 certificate                                       │
│                                                                          │
│  ✅ Strongest security                                                   │
│  ✅ Zero-trust architecture                                              │
│  ❌ Certificate management overhead                                      │
│  ❌ Not suitable for public APIs                                        │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

6.2 Auth Decision Matrix

Use CaseRecommendedWhy
Public API (3rd party)OAuth 2.0 + API KeysScoped access, revocable
Mobile/SPAOAuth 2.0 + PKCESecure without client secret
Microservices (internal)mTLS / JWTZero-trust, service identity
Server-to-serverAPI Keys / mTLSSimple, secure
Real-time (WebSocket)JWTStateless validation

7. Rate Limiting Patterns

7.1 Token Bucket Algorithm

┌─────────────────────────────────────────────────────────────────────────┐
│                       TOKEN BUCKET ALGORITHM                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   ┌─────────────────────────────────────────┐                           │
│   │           TOKEN BUCKET                   │                           │
│   │   ┌───┬───┬───┬───┬───┬───┬───┬───┐    │                           │
│   │   │ ● │ ● │ ● │ ● │ ● │   │   │   │    │  Capacity: 10 tokens      │
│   │   └───┴───┴───┴───┴───┴───┴───┴───┘    │  Current: 5 tokens        │
│   │        ▲                                 │                           │
│   │        │ Refill rate: 2 tokens/second   │                           │
│   └────────┼────────────────────────────────┘                           │
│            │                                                             │
│   Request ─┴─► Take 1 token                                             │
│                                                                          │
│   if (bucket.tokens >= 1) {                                             │
│     bucket.tokens -= 1;                                                 │
│     processRequest();       // ✅ Allowed                               │
│   } else {                                                               │
│     return 429;             // ❌ Rate limited                          │
│   }                                                                      │
│                                                                          │
│   Burst handling: Bucket can accumulate tokens up to capacity           │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

7.2 Rate Limit Headers

http
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640995200
Retry-After: 60

7.3 Distributed Rate Limiting

typescript
// Redis-based distributed rate limiter
async function checkRateLimit(userId: string): Promise<boolean> {
  const key = `ratelimit:${userId}`;
  const limit = 100;
  const window = 60; // 60 seconds

  const current = await redis.incr(key);
  
  if (current === 1) {
    await redis.expire(key, window);
  }

  if (current > limit) {
    const ttl = await redis.ttl(key);
    throw new RateLimitError({
      retryAfter: ttl,
      limit,
      remaining: 0
    });
  }

  return true;
}

7.4 Rate Limiting Strategies

StrategyDescriptionUse Case
Per UserLimit by user IDMost common
Per IPLimit by client IPUnauthenticated endpoints
Per API KeyLimit by keyB2B APIs
Per EndpointDifferent limits per routeExpensive operations
GlobalProtect overall systemDDoS protection

8. Backward Compatibility & Schema Evolution

8.1 Non-Breaking Changes

typescript
// ✅ SAFE: Adding optional field
// v1.0
interface UserV1 {
  id: string;
  name: string;
}

// v1.1 - Adding optional field (backward compatible)
interface UserV1_1 {
  id: string;
  name: string;
  avatar?: string;  // New optional field
}

8.2 Deprecation Strategy

┌─────────────────────────────────────────────────────────────────────────┐
│                      DEPRECATION TIMELINE                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Month 0          Month 3          Month 6          Month 12            │
│     │                │                │                │                 │
│     ▼                ▼                ▼                ▼                 │
│  ┌──────┐        ┌──────┐        ┌──────┐        ┌──────┐              │
│  │v1 GA │───────►│v2 GA │───────►│v1 Dep│───────►│v1 EOL│              │
│  └──────┘        │v1 Dep│        │v2 GA │        │v2 GA │              │
│                  │Warn  │        │      │        │      │              │
│                  └──────┘        └──────┘        └──────┘              │
│                                                                          │
│  Deprecation headers:                                                    │
│  Deprecation: Sun, 01 Jan 2025 00:00:00 GMT                             │
│  Sunset: Sun, 01 Jul 2025 00:00:00 GMT                                  │
│  Link: <https://api.example.com/v2/docs>; rel="successor-version"       │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

8.3 Protobuf Schema Evolution Rules

ChangeBackward Compatible?Forward Compatible?
Add optional field✅ Yes✅ Yes
Add required field❌ No❌ No
Remove field✅ Yes*❌ No
Change field type❌ No❌ No
Rename field✅ Yes (same number)✅ Yes
Change field number❌ No❌ No

*Only if you never reuse the field number


9. Observability Hooks

9.1 Structured Request/Response Logging

typescript
// Middleware for structured logging
function observabilityMiddleware(req, res, next) {
  const startTime = Date.now();
  const traceId = req.headers['x-trace-id'] || generateTraceId();

  // Inject trace ID into context
  req.traceId = traceId;
  res.setHeader('X-Trace-Id', traceId);

  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    logger.info({
      // Standard fields
      timestamp: new Date().toISOString(),
      traceId,
      spanId: generateSpanId(),
      
      // Request info
      method: req.method,
      path: req.path,
      query: req.query,
      userAgent: req.headers['user-agent'],
      
      // Response info
      statusCode: res.statusCode,
      durationMs: duration,
      
      // Custom dimensions
      userId: req.user?.id,
      apiVersion: req.headers['api-version'],
    });
  });

  next();
}

9.2 Metrics to Track

MetricTypeDescription
api_requests_totalCounterTotal requests by endpoint, method, status
api_request_duration_msHistogramLatency distribution
api_active_requestsGaugeCurrently processing
api_error_rateRateErrors per second
api_rate_limit_hitsCounterRate limit violations

9.3 Distributed Tracing Headers

http
# W3C Trace Context (Standard)
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: vendor=value

# B3 (Zipkin)
X-B3-TraceId: 80f198ee56343ba864fe8b2a57d3eff7
X-B3-SpanId: e457b5a2e4d86bd1
X-B3-Sampled: 1

10. Anti-Patterns to Avoid

CAUTION

🔧 Kỹ sư Raizo: Những patterns này tôi đã thấy trong production và chúng luôn gây ra pain. Hãy tránh ngay từ đầu.

10.1 Common API Design Mistakes

┌─────────────────────────────────────────────────────────────────────────┐
│                      ⚠️ API ANTI-PATTERNS                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ❌ 1. CHATTY APIs                                                       │
│  ──────────────────────────────────────────────────────────────────────  │
│  // Bad: N+1 requests                                                    │
│  GET /orders           → List order IDs                                  │
│  GET /orders/1/items   → Get items for order 1                          │
│  GET /orders/2/items   → Get items for order 2                          │
│  ...repeat N times                                                       │
│                                                                          │
│  ✅ Fix: Compound documents or GraphQL                                   │
│  GET /orders?include=items                                               │
│                                                                          │
│  ──────────────────────────────────────────────────────────────────────  │
│  ❌ 2. LEAKING INTERNAL IDs                                              │
│  ──────────────────────────────────────────────────────────────────────  │
│  // Bad: Exposes auto-increment IDs                                      │
│  GET /users/1, /users/2, /users/3                                        │
│  → Attacker can enumerate all users                                      │
│                                                                          │
│  ✅ Fix: Use UUIDs or public ID aliases                                  │
│  GET /users/usr_abc123xyz                                                │
│                                                                          │
│  ──────────────────────────────────────────────────────────────────────  │
│  ❌ 3. VERB IN URL                                                       │
│  ──────────────────────────────────────────────────────────────────────  │
│  // Bad: Actions as URLs                                                 │
│  POST /users/123/activate                                                │
│  POST /orders/456/cancel                                                 │
│                                                                          │
│  ✅ Fix: Use resources and HTTP methods                                  │
│  PATCH /users/123 { "status": "active" }                                 │
│  DELETE /orders/456                                                      │
│                                                                          │
│  ──────────────────────────────────────────────────────────────────────  │
│  ❌ 4. INCONSISTENT NAMING                                               │
│  ──────────────────────────────────────────────────────────────────────  │
│  // Bad: Mixed conventions                                               │
│  GET /users, /order-items, /ProductCategories                            │
│                                                                          │
│  ✅ Fix: Consistent kebab-case or snake_case                             │
│  GET /users, /order-items, /product-categories                           │
│                                                                          │
│  ──────────────────────────────────────────────────────────────────────  │
│  ❌ 5. IGNORING FUTURE EXTENSIBILITY                                     │
│  ──────────────────────────────────────────────────────────────────────  │
│  // Bad: No envelope                                                     │
│  [{ "id": 1 }, { "id": 2 }]                                              │
│                                                                          │
│  ✅ Fix: Response envelope                                               │
│  { "data": [...], "meta": { "total": 100 } }                            │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

10.2 Security Anti-Patterns

Anti-PatternRiskFix
API keys in URLKeys logged, cachedUse Authorization header
No rate limitingDoS attacksImplement per-user limits
Verbose errors in prodInfo disclosureGeneric errors + traceId
Missing input validationInjection attacksValidate + sanitize all input
CORS * wildcardCSRF attacksWhitelist allowed origins

📝 Summary

ConceptKey Takeaway
REST vs GraphQL vs gRPCREST for public, GraphQL for complex queries, gRPC for internal
VersioningURL path versioning is most practical for most teams
PaginationUse cursor-based for large datasets, offset for simple cases
IdempotencyAlways require Idempotency-Key for POST requests
Error ModelRFC 7807 Problem Details + consistent error codes
Rate LimitingToken bucket, always implement per-user + global limits
Backward CompatOnly add optional fields, deprecate with 6+ month notice
ObservabilityTrace ID in all requests, structured logging, standard metrics

🚀 Next Steps

TopicWhy
📡 Sync CommunicationDeep dive into HTTP/gRPC internals
📬 Async MessagingWhen APIs alone aren't enough
🔄 Distributed TransactionsSaga pattern for cross-service operations

Apply in Case Studies

Case StudyAPI Learnings
🐦 Design TwitterTimeline API pagination
💬 Design WhatsAppReal-time message delivery
🚗 Design UberLocation streaming APIs

👉 Quay lại System Design Universe →