Featured image of post REST vs. GraphQL vs. gRPC: Mô hình tư duy 'Thực đơn Nhà hàng'

REST vs. GraphQL vs. gRPC: Mô hình tư duy 'Thực đơn Nhà hàng'

Tại sao GraphQL lại ra đời nếu REST đã ổn? Hướng dẫn chuyên sâu về giao thức API, khi nào dùng cái nào, và tại sao gRPC thay đổi cuộc chơi cho các service nội bộ.

“Chúng ta có nên dùng GraphQL không?”

Team chia đôi ngay lập tức. Một nửa bảo xịn lắm. Nửa kia bảo REST thế là ổn rồi. Không ai giải thích được sự khác biệt mà không phải dùng đến câu “it depends.”

Bài viết này giải quyết dứt điểm.

Đây là Hướng dẫn chuyên sâu về Giao thức API. Chúng ta sẽ dùng mô hình “Thực đơn Nhà hàng” để hiểu REST (Thực đơn in sẵn), GraphQL (Gọi món tùy ý) và gRPC (Bộ đàm giữa các đầu bếp).


Phần 1: Nền tảng (Mô hình tư duy)

REST = Thực đơn In sẵn

REST API giống như nhà hàng có thực đơn in sẵn. Bếp quyết định có những món gì. Bạn chọn từ danh sách đó.

  • Điểm mạnh: Đơn giản, phổ quát. Mọi ngôn ngữ, mọi công cụ (Postman, curl) đều hiểu.
  • Điểm yếu: Bạn nhận đúng cái có trên thực đơn. Nếu chỉ muốn cái salad trong “Combo Gà Nướng + Salad,” bạn vẫn phải trả tiền cả combo. (Over-fetching). Hoặc thực đơn không có đúng thứ bạn cần; bồi bàn phải mang ba món riêng ra. (Under-fetching).

GraphQL = Gọi món Tùy ý

GraphQL giống như nhà hàng nơi bạn nói chính xác với đầu bếp những gì bạn muốn.

  • “Cho tôi ức gà không nước sốt, thêm cơm, và sốt salad xoài.”
  • Bếp làm đúng cái đó. Một chuyến bưng. Đúng những gì bạn yêu cầu.
  • Điểm mạnh: Không over-fetching. Không under-fetching. Cực tốt cho mobile app nơi băng thông có hạn.
  • Điểm yếu: Bếp phức tạp hơn. Phải quản lý schema. Phân quyền khó hơn (user này có được hỏi user.creditCard không?).

gRPC = Bộ đàm giữa các đầu bếp

gRPC không dành cho khách hàng. Nó dành cho giao tiếp bếp-với-bếp.

  • Đầu bếp Bộ phận Đồ lạnh gọi bộ đàm cho Bộ phận Đồ nóng: “Bít tết xong chưa?”
  • Dùng giao thức nhị phân (không phải text có thể đọc được) để tối đa tốc độ.
  • Strongly typed. Hợp đồng được định nghĩa trong file .proto.
  • Điểm mạnh: Nhanh gấp bội (3x-10x JSON/REST). Hỗ trợ streaming hai chiều.
  • Điểm yếu: Không dùng được trên browser. File .proto cần tooling chung.

Phần 2: Điều tra (Khác biệt kỹ thuật)

1. Vấn đề N+1 (Gót chân Achilles của REST)

Một UI hiển thị danh sách bài viết (Posts) kèm tên tác giả.

1
2
3
4
5
6
7
# 1 request cho danh sách bài viết
GET /posts          → [{ id: 1, author_id: 5 }, { id: 2, author_id: 8 }, ...]

# Rồi N request cho từng tác giả!
GET /users/5        → { name: "Alice" }
GET /users/8        → { name: "Bob" }
... (50 bài viết → 51 lần gọi API)

GraphQL giải quyết: Một query, lấy đúng dữ liệu cần.

1
2
3
4
5
6
7
8
query {
  posts {
    title
    author {
      name  # GraphQL giải quyết trong MỘT lần gọi
    }
  }
}

2. gRPC: Hợp đồng Proto

File .proto là ADN của gRPC — ngôn ngữ chung mà cả client lẫn server đều hiểu.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// payment.proto
syntax = "proto3";

service PaymentService {
  rpc ProcessPayment (PaymentRequest) returns (PaymentResponse);
  rpc StreamUpdates (PaymentRequest) returns (stream PaymentEvent);  // Streaming!
}

message PaymentRequest {
  string order_id = 1;
  double amount = 2;
}

message PaymentResponse {
  bool success = 1;
  string transaction_id = 2;
}

Từ một file này, protoc tự sinh ra code client và server strongly-typed cho Python, Go, Java,…


Phần 3: Chẩn đoán (Khi nào dùng gì?)

Use CaseLựa chọn tốt nhấtTại sao
API Công khai (dev bên thứ ba)RESTPhổ quát — ngôn ngữ nào cũng dùng được.
Mobile App (băng thông quan trọng)GraphQLLấy đúng dữ liệu màn hình cần, không hơn không kém.
Microservice Nội bộgRPCNhanh, type-safe, hỗ trợ streaming.
Dữ liệu Realtime (chat, feed)GraphQL Subscriptions hoặc gRPC StreamingStreaming được tích hợp sẵn.
CRUD đơn giảnRESTĐừng phức tạp hóa không cần thiết.
BFF (Backend for Frontend)GraphQLTổng hợp nhiều service cho một UI.

Phần 4: Giải pháp (Code Examples)

1. REST (Python/Django)

1
2
3
4
5
6
7
8
# urls.py
path("posts/<int:pk>/", PostDetailView.as_view()),

# views.py
class PostDetailView(APIView):
    def get(self, request, pk):
        post = Post.objects.select_related("author").get(pk=pk)
        return Response(PostSerializer(post).data)

2. GraphQL (Python/Strawberry)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import strawberry
from typing import List

@strawberry.type
class Author:
    name: str

@strawberry.type
class Post:
    title: str
    author: Author

@strawberry.type
class Query:
    @strawberry.field
    def posts(self) -> List[Post]:
        return Post.objects.select_related("author").all()

schema = strawberry.Schema(query=Query)

3. gRPC (Python)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# server.py (Payment Service)
class PaymentServicer(payment_pb2_grpc.PaymentServiceServicer):
    def ProcessPayment(self, request, context):
        # request.order_id, request.amount là strongly typed!
        success = charge(request.amount)
        return payment_pb2.PaymentResponse(
            success=success,
            transaction_id="txn_123"
        )

# client.py (Order Service gọi Payment Service)
channel = grpc.insecure_channel("payment-service:50051")
stub = payment_pb2_grpc.PaymentServiceStub(channel)
response = stub.ProcessPayment(
    payment_pb2.PaymentRequest(order_id="ord_456", amount=99.99)
)

Mô hình tư duy chốt hạ

1
2
3
4
5
6
7
REST     -> Thực đơn in sẵn. Phổ quát, dễ đoán, đôi khi lãng phí.
GraphQL  -> Gọi món tùy ý. Chính xác, mạnh, phức tạp để phân quyền.
gRPC     -> Bộ đàm đầu bếp. Nhanh, typed, chỉ dùng nội bộ.

Over-fetching  -> "Tôi hỏi thông tin user nhưng nhận về cả lịch sử đơn hàng."
Under-fetching -> "Cần tên tác giả, nhưng /posts chỉ cho author_id."
N+1 Problem    -> "50 bài = 51 lần gọi API." (GraphQL hoặc SQL JOIN giải quyết được)

Bắt đầu với REST. Chuyển sang GraphQL chỉ khi client mobile than phiền về lượng data. Chuyển sang gRPC khi latency của service nội bộ trở thành nút cổ chai.

Được tạo với sự lười biếng tình yêu 🦥

Subscribe to My Newsletter