Giao diện
📡 SYNCHRONOUS COMMUNICATION
REST vs gRPC vs GraphQL: Cuộc chiến của các Giao thức
Mục tiêu Học tập
Sau khi hoàn thành module này, bạn sẽ có khả năng:
- Phân tích trade-offs giữa REST, gRPC, và GraphQL
- Hiểu tại sao gRPC tối ưu cho Service-to-Service communication
- Giải quyết vấn đề Over-fetching và Under-fetching
- Áp dụng Contract-first Design với Protocol Buffers
1. The Fallacy of "One Size Fits All"
NOTE
🎓 Giáo sư Tom: Một trong những sai lầm lớn nhất trong API design là dùng cùng một protocol cho mọi thứ. REST tuyệt vời cho public APIs, nhưng là "overkill" cho internal service calls. gRPC nhanh hơn 10x trong microservices, nhưng browsers không support native. Hiểu context là key.
┌─────────────────────────────────────────────────────────────────────────┐
│ COMMUNICATION ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ EXTERNAL CLIENTS │ │
│ │ │ │
│ │ 🌐 Browser 📱 Mobile App 🤖 Third-party API Consumer │ │
│ │ │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ API GATEWAY │ │
│ │ (REST/GraphQL) │ ◄─── Human-readable │
│ │ │ Browser compatible │
│ └─────────┬─────────┘ │
│ │ │
│ ══════════════════════════╪════════════════════════════════════════ │
│ │ INTERNAL NETWORK │
│ ══════════════════════════╪════════════════════════════════════════ │
│ │ │
│ ┌──────────────────────────▼──────────────────────────────────────┐ │
│ │ MICROSERVICES │ │
│ │ │ │
│ │ ┌─────────┐ gRPC ┌─────────┐ gRPC ┌─────────┐ │ │
│ │ │ Service │◄──────────►│ Service │◄──────────►│ Service │ │ │
│ │ │ A │ │ B │ │ C │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ ◄─── Binary protocol, code generation, type safety ─────────►│ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘2. REST: The Workhorse
2.1 REST Principles
┌─────────────────────────────────────────────────────────────────────────┐
│ REST FUNDAMENTALS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CORE CONSTRAINTS: │
│ ═════════════════ │
│ │
│ 1. STATELESS │
│ Each request contains ALL information needed │
│ Server doesn't store client context between requests │
│ │
│ 2. UNIFORM INTERFACE │
│ GET → Read resource │
│ POST → Create resource │
│ PUT → Replace resource │
│ PATCH → Partial update │
│ DELETE → Remove resource │
│ │
│ 3. RESOURCE-BASED │
│ URLs represent resources (nouns), not actions (verbs) │
│ │
│ ✅ GET /users/123 │
│ ❌ GET /getUser?id=123 │
│ ❌ POST /createUser │
│ │
│ 4. CACHEABLE │
│ Response should be cacheable when appropriate │
│ Cache-Control, ETag, Last-Modified headers │
│ │
└─────────────────────────────────────────────────────────────────────────┘2.2 REST Problems: Over-fetching & Under-fetching
┌─────────────────────────────────────────────────────────────────────────┐
│ OVER-FETCHING PROBLEM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Mobile App needs: User's name and avatar (for header) │
│ │
│ Request: GET /api/users/123 │
│ │
│ Response (500 bytes): │
│ { │
│ "id": 123, │
│ "name": "Nguyen Van A", ◄─── NEEDED │
│ "avatar": "https://...", ◄─── NEEDED │
│ "email": "a@example.com", ◄─── WASTED │
│ "phone": "0901234567", ◄─── WASTED │
│ "address": "123 Nguyen Hue", ◄─── WASTED │
│ "bio": "Full-stack dev...", ◄─── WASTED │
│ "createdAt": "2023-01-15", ◄─── WASTED │
│ "settings": { ... }, ◄─── WASTED │
│ "subscriptions": [ ... ] ◄─── WASTED │
│ } │
│ │
│ Mobile only needed 50 bytes, got 500 bytes → 10x waste │
│ × 10,000 users = 5MB wasted bandwidth │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ UNDER-FETCHING PROBLEM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Frontend needs: User + Recent Orders + Favorite Products │
│ │
│ With REST: │
│ ══════════ │
│ Request 1: GET /api/users/123 │
│ Request 2: GET /api/users/123/orders?limit=5 │
│ Request 3: GET /api/users/123/favorites │
│ │
│ = 3 round trips = 3 × latency │
│ │
│ If each request = 100ms latency: │
│ Total = 300ms (serial) or 100ms (parallel, but 3x connections) │
│ │
│ Mobile on 3G: Each request = 500ms │
│ Total = 1.5 seconds just for API calls! │
│ │
│ Alternative: Create custom endpoint │
│ GET /api/users/123/dashboard │
│ → But now you have N endpoints for N screens │
│ → Backend becomes "frontend's slave" │
│ │
└─────────────────────────────────────────────────────────────────────────┘2.3 When to Use REST
| Scenario | REST Suitability |
|---|---|
| Public API | ✅ Excellent - universally understood |
| Third-party integration | ✅ Everyone knows REST |
| Browser direct calls | ✅ Native support |
| Simple CRUD | ✅ Maps perfectly |
| Internal microservices | ⚠️ Consider gRPC |
| Real-time data | ❌ Use WebSockets/gRPC streaming |
| High-throughput internal | ❌ REST overhead too high |
3. gRPC: The Performance Monster
3.1 What is gRPC?
TIP
🔧 Kỹ sư Raizo: gRPC = gRPC Remote Procedure Call (recursive acronym!). Google dùng nội bộ từ 2001 với tên Stubby. Open-sourced năm 2015. Mọi Google service đều chạy trên gRPC - hàng tỷ RPC calls/second.
┌─────────────────────────────────────────────────────────────────────────┐
│ gRPC ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ .proto FILE │ │
│ │ │ │
│ │ syntax = "proto3"; │ │
│ │ │ │
│ │ service UserService { │ │
│ │ rpc GetUser(UserRequest) returns (UserResponse); │ │
│ │ rpc ListUsers(ListRequest) returns (stream User); │ │
│ │ } │ │
│ │ │ │
│ │ message UserRequest { │ │
│ │ int32 user_id = 1; │ │
│ │ } │ │
│ │ │ │
│ │ message UserResponse { │ │
│ │ int32 id = 1; │ │
│ │ string name = 2; │ │
│ │ string email = 3; │ │
│ │ } │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Go Generated │ │ Python Gen │ │ Rust Gen │ │
│ │ Client + Server │ │ Client + │ │ Client + │ │
│ │ │ │ Server │ │ Server │ │
│ └─────────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Code generation = Type safety across ALL languages! │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.2 Protobuf: Binary vs JSON
┌─────────────────────────────────────────────────────────────────────────┐
│ PROTOBUF vs JSON ENCODING │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Same data: User { id: 123, name: "Nguyen", active: true } │
│ │
│ JSON (67 bytes): │
│ ═══════════════ │
│ { │
│ "id": 123, ← 10 chars for field name │
│ "name": "Nguyen", ← Quotes, colons, commas │
│ "active": true ← Boolean as 4-5 chars │
│ } │
│ │
│ Protobuf (11 bytes): │
│ ════════════════════ │
│ 08 7B 12 06 4E 67 75 79 65 6E 18 01 │
│ ├──┤ ├───────────────────────┤ ├────┤ │
│ id=123 name="Nguyen" active=1 │
│ │
│ Field names? Không encode! Chỉ dùng field number (1, 2, 3) │
│ Integers? Varint encoding (nhỏ nhất có thể) │
│ Boolean? 1 byte │
│ │
│ Result: 67 bytes → 11 bytes = 83% smaller! │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ PARSING SPEED: │
│ ══════════════ │
│ │
│ JSON: │
│ 1. Tokenize string │
│ 2. Parse field names (string compare) │
│ 3. Type conversion (string "123" → int 123) │
│ 4. Build object │
│ │
│ Protobuf: │
│ 1. Read field tag (1 byte: field number + type) │
│ 2. Read value directly (already correct type) │
│ 3. Done │
│ │
│ Benchmark: Protobuf parse 5-10x faster than JSON │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.3 gRPC Streaming Modes
┌─────────────────────────────────────────────────────────────────────────┐
│ gRPC STREAMING MODES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. UNARY (như REST) │
│ ════════════════════ │
│ Client ─────── Request ──────► Server │
│ ◄────── Response ─────── │
│ │
│ Use: Simple request/response │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 2. SERVER STREAMING │
│ ════════════════════ │
│ Client ─────── Request ──────► Server │
│ ◄────── Response 1 ───── │
│ ◄────── Response 2 ───── │
│ ◄────── Response N ───── │
│ ◄────── END ──────────── │
│ │
│ Use: Download large dataset, real-time updates │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 3. CLIENT STREAMING │
│ ════════════════════ │
│ Client ─────── Request 1 ─────► Server │
│ ─────── Request 2 ─────► │
│ ─────── Request N ─────► │
│ ─────── END ───────────► │
│ ◄────── Response ─────── │
│ │
│ Use: Upload large file, batch insert │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 4. BIDIRECTIONAL STREAMING │
│ ═══════════════════════════ │
│ Client ─────── Request 1 ─────► Server │
│ ◄────── Response 1 ───── │
│ ─────── Request 2 ─────► │
│ ◄────── Response 2 ───── │
│ ◄────── Response 3 ───── │
│ ─────── Request 3 ─────► │
│ │
│ Use: Chat, real-time collaboration, gaming │
│ │
└─────────────────────────────────────────────────────────────────────────┘3.4 gRPC Limitations
WARNING
gRPC không phải silver bullet:
- ❌ Browser không support native (cần gRPC-Web proxy)
- ❌ Không human-readable (binary format)
- ❌ Khó debug với curl/Postman (cần grpcurl)
- ❌ Learning curve cho team chưa quen
4. GraphQL: The Flexible One
4.1 How GraphQL Works
┌─────────────────────────────────────────────────────────────────────────┐
│ GRAPHQL QUERY EXAMPLE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CLIENT QUERY: │
│ ═════════════ │
│ query { │
│ user(id: 123) { │
│ name ← Only what I need │
│ avatar ← Only what I need │
│ recentOrders(limit: 5) { │
│ id │
│ total │
│ status │
│ } │
│ favoriteProducts(limit: 3) { │
│ name │
│ price │
│ } │
│ } │
│ } │
│ │
│ RESPONSE: │
│ ═════════ │
│ { │
│ "data": { │
│ "user": { │
│ "name": "Nguyen Van A", ← Exactly what asked │
│ "avatar": "https://...", │
│ "recentOrders": [ ← Nested in ONE request │
│ { "id": 1, "total": 100, "status": "delivered" }, │
│ { "id": 2, "total": 200, "status": "shipped" } │
│ ], │
│ "favoriteProducts": [ │
│ { "name": "T-Shirt", "price": 25 } │
│ ] │
│ } │
│ } │
│ } │
│ │
│ ✅ NO over-fetching (only requested fields) │
│ ✅ NO under-fetching (nested resources in 1 request) │
│ ✅ Frontend controls shape of response │
│ │
└─────────────────────────────────────────────────────────────────────────┘4.2 GraphQL Trade-offs
| Advantage | Disadvantage |
|---|---|
| Flexible queries | Complexity in backend |
| No over/under-fetching | Caching is HARD |
| Single endpoint | N+1 query problem |
| Strong typing | Security concerns (DoS via complex queries) |
| Self-documenting | Learning curve |
4.3 The Caching Problem
┌─────────────────────────────────────────────────────────────────────────┐
│ GRAPHQL CACHING CHALLENGE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ REST: │
│ ═════ │
│ GET /users/123 → Cache key: "/users/123" │
│ GET /users/123 → HIT! Return cached response │
│ │
│ GraphQL: │
│ ════════ │
│ POST /graphql │
│ { query: "{ user(id:123) { name } }" } │
│ │
│ POST /graphql │
│ { query: "{ user(id:123) { name email } }" } ← Different query! │
│ │
│ Cache key = query string? But queries are INFINITE │
│ → Need normalized cache (Apollo Client) │
│ → More complex client-side logic │
│ │
│ CDN Caching: │
│ ════════════ │
│ REST: CloudFlare/Fastly cache GET requests perfectly │
│ GraphQL: POST requests = no CDN caching by default │
│ → Workaround: Persisted queries (hash query → GET) │
│ │
└─────────────────────────────────────────────────────────────────────────┘5. Comparison Matrix
5.1 Feature Comparison
| Criteria | REST | gRPC | GraphQL |
|---|---|---|---|
| Payload Format | JSON (text) | Protobuf (binary) | JSON (text) |
| Payload Size | Large | Small (60-80% less) | Medium |
| Human Readable | ✅ Yes | ❌ No | ✅ Yes |
| Browser Native | ✅ Yes | ❌ No (needs proxy) | ✅ Yes |
| Code Generation | Optional | ✅ Built-in | Optional |
| Streaming | ❌ Limited | ✅ Native support | ❌ Subscriptions only |
| Caching | ✅ Easy (HTTP) | ⚠️ Manual | ⚠️ Complex |
| Learning Curve | Low | Medium | High |
5.2 Performance Comparison
| Metric | REST | gRPC | GraphQL |
|---|---|---|---|
| Latency | Baseline | 50-80% lower | Similar to REST |
| Throughput | Baseline | 5-10x higher | Varies |
| Serialization CPU | High | Low | High |
| Network Bandwidth | High | Low | Medium |
5.3 Use Case Matrix
┌─────────────────────────────────────────────────────────────────────────┐
│ WHEN TO USE WHAT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ USE REST WHEN: │
│ ══════════════ │
│ • Public API (third-party developers) │
│ • Simple CRUD operations │
│ • Maximum compatibility needed │
│ • Team new to API development │
│ │
│ USE gRPC WHEN: │
│ ══════════════ │
│ • Internal microservices communication │
│ • High-performance requirements │
│ • Polyglot environment (Go ↔ Rust ↔ Python) │
│ • Streaming data needed │
│ • Team comfortable with Protobuf │
│ │
│ USE GRAPHQL WHEN: │
│ ════════════════ │
│ • Mobile app with varying data needs │
│ • Multiple frontend clients (web, iOS, Android) │
│ • Rapid frontend iteration needed │
│ • Over-fetching is major problem │
│ • Team has GraphQL expertise │
│ │
└─────────────────────────────────────────────────────────────────────────┘6. Raizo's Production Advice
TIP
🔧 Kỹ sư Raizo - HPN Architecture:
Penrift Cloud sử dụng hybrid approach:
- External API: REST với OpenAPI spec (developer-friendly)
- Internal Services: gRPC (Go services ↔ Rust services)
- Admin Dashboard: GraphQL (rapid iteration needed)
Golden Rule: Use the right tool for the right job, không phải "cool" nhất.
6.1 gRPC Best Practices
protobuf
// ✅ GOOD: Specific message types
message CreateUserRequest {
string name = 1;
string email = 2;
}
message CreateUserResponse {
User user = 1;
Error error = 2; // Explicit error handling
}
// ❌ BAD: Generic catch-all
message GenericRequest {
map<string, string> data = 1;
}6.2 Contract-First Development Flow
┌─────────────────────────────────────────────────────────────────────────┐
│ CONTRACT-FIRST WORKFLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. DESIGN PHASE │
│ ═══════════════ │
│ Team discusses & writes .proto file together │
│ │
│ 2. REVIEW │
│ ═════════ │
│ PR review for .proto changes │
│ Breaking changes? Bump version │
│ │
│ 3. GENERATE │
│ ══════════ │
│ protoc --go_out=. --go-grpc_out=. user.proto │
│ protoc --python_out=. --grpc_python_out=. user.proto │
│ │
│ 4. IMPLEMENT │
│ ════════════ │
│ Server: Implement generated interface │
│ Client: Use generated stubs │
│ │
│ 5. TEST │
│ ═══════ │
│ Contract tests ensure compatibility │
│ │
│ ✅ Type-safe across languages │
│ ✅ Breaking changes caught at compile time │
│ ✅ Auto-generated documentation │
│ │
└─────────────────────────────────────────────────────────────────────────┘7. Scenario Quiz
Câu hỏi 1: Startup API Design
Bạn build MVP cho startup. Team có 3 devs, product đang thay đổi hàng tuần. Mobile app cần data từ 5 endpoints khác nhau cho 1 màn hình.
Bạn chọn REST, gRPC, hay GraphQL? Tại sao?
👁️ Xem đáp án
Đáp án: GraphQL (hoặc REST với BFF pattern)
Phân tích:
- Team size nhỏ → Learning curve của gRPC không worth it
- Product thay đổi nhanh → GraphQL flexibility cho phép frontend tự adapt
- 5 endpoints cho 1 màn hình → Under-fetching problem → GraphQL solves this
- Mobile app → Bandwidth sensitivity → GraphQL giảm over-fetching
Alternatives:
- REST + BFF (Backend for Frontend): Tạo
/api/mobile/home-screenendpoint - REST + Composite endpoints: Merge nhiều resources vào 1 response
Không chọn gRPC vì:
- Browser/Mobile không support native
- Setup complexity cho small team
- Debugging khó hơn trong early stage
Câu hỏi 2: High-throughput Internal Services
Bạn có 50 microservices trong Kubernetes cluster. Mỗi service gọi 10 services khác. Latency requirement: P99 < 10ms cho internal calls.
Bạn chọn protocol nào?
👁️ Xem đáp án
Đáp án: gRPC
Lý do:
- P99 < 10ms → REST JSON parsing overhead quá cao
- 50 services, polyglot → gRPC code generation ensures type safety
- Internal only → Không cần browser compatibility
- High call volume → Binary encoding saves bandwidth
Implementation notes:
yaml
# Service mesh (Istio/Linkerd) với gRPC load balancing
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
spec:
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
connectionPool:
http:
h2UpgradePolicy: UPGRADE # HTTP/2 for gRPCMonitoring:
- gRPC errors → Standardized status codes
- Latency → Interceptors for metrics
- Tracing → OpenTelemetry integration
8. Tiếp theo
Bây giờ bạn đã hiểu về Synchronous Communication, hãy khám phá:
Chúng ta sẽ so sánh Kafka vs RabbitMQ - và hiểu khi nào dùng message queue thay vì synchronous calls.