gRPC — REST Và GraphQL Chưa Đủ Cho Microservices
gRPC là giao thức API tốc độ cao dùng Protobuf và HTTP/2. So sánh với REST, GraphQL, code demo, và khi nào nên dùng.
REST thống trị API trong một thập kỷ. GraphQL đến và giải quyết over-fetching. Nhưng cả hai đều sinh ra với JSON và HTTP/1.1 — công nghệ của 20 năm trước. Khi bạn cần giao tiếp giữa các microservices với tốc độ cao, low latency, streaming — gRPC là lựa chọn tối ưu.
gRPC Là Gì?#
gRPC là framework RPC (Remote Procedure Call) mã nguồn mở của Google. Thay vì gọi HTTP endpoint và nhận JSON, bạn gọi function như local — dữ liệu được serialize bằng Protocol Buffers và truyền qua HTTP/2.
// Định nghĩa service trong .proto file
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
}
message GetUserRequest {
string id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}protobufSau đó code gen ra client/server code tự động:
// Server
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, err := s.db.FindUser(req.Id)
if err != nil {
return nil, err
}
return &pb.User{Id: user.ID, Name: user.Name, Email: user.Email}, nil
}
// Client — gọi như function local
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
fmt.Println(user.Name)goProtocol Buffers#
Thay vì JSON (text, ~200-500 bytes cho object nhỏ), Protobuf là binary format:
- Nhỏ hơn: 10x nhỏ hơn JSON
- Nhanh hơn: Encode/decode nhanh hơn JSON ~10-100x
- Typed: Schema bắt buộc, code gen tự động
- Backward compatible: Thêm field mới không break client cũ
syntax = "proto3";
message Product {
string id = 1;
string name = 2;
double price = 3;
repeated string tags = 4; // Array
bool available = 5;
oneof source { // Union
string internal_id = 6;
string external_url = 7;
}
}protobufHTTP/2 Lợi Thế#
gRPC dùng HTTP/2 (không phải HTTP/1.1 như REST truyền thống):
- Multiplexing: Nhiều request song song trên một connection — không cần connection pool
- Header compression: HPACK — header chỉ vài bytes sau lần đầu
- Server push: Server gửi dữ liệu trước khi client request
- Streaming: Một connection có thể gửi/nhận nhiều message liên tục
Bốn Kiểu API#
Unary RPC (giống REST)#
rpc GetUser(GetUserRequest) returns (User);
// Request → ResponseprotobufServer Streaming#
rpc ListUsers(ListUsersRequest) returns (stream User);
// Request → nhiều Response (real-time feed)protobufClient Streaming#
rpc UploadFile(stream FileChunk) returns (UploadResponse);
// Nhiều Request → một ResponseprotobufBidirectional Streaming#
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
// Nhiều Request ↔ nhiều Response (WebSocket-like)protobufBidirectional streaming là killer feature — chat, real-time data, gaming đều dùng pattern này.
Code Demo: User Service#
Proto#
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc SearchUsers (SearchRequest) returns (stream User);
}
message GetUserRequest { string id = 1; }
message SearchRequest { string query = 1; }
message User {
string id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}protobufGenerate Code#
protoc --go_out=. --go-grpc_out=. user.proto
# Sinh ra user.pb.go và user_grpc.pb.gobashServer (Go)#
type server struct {
pb.UnimplementedUserServiceServer
db map[string]*pb.User
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, ok := s.db[req.Id]
if !ok {
return nil, status.Errorf(codes.NotFound, "user not found")
}
return user, nil
}
func (s *server) SearchUsers(req *pb.SearchRequest, stream pb.UserService_SearchUsersServer) error {
for _, user := range s.db {
if strings.Contains(user.Name, req.Query) {
if err := stream.Send(user); err != nil {
return err
}
}
}
return nil
}goClient (Go)#
conn, _ := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure))
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// Unary call
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
// Streaming call
stream, _ := client.SearchUsers(ctx, &pb.SearchRequest{Query: "Alice"})
for {
user, err := stream.Recv()
if err == io.EOF { break }
fmt.Println(user.Name)
}goClient (Node.js)#
const client = new proto.UserService('localhost:50051', grpc.credentials.createInsecure())
// Unary
client.getUser({ id: '123' }, (err, user) => {
console.log(user.name)
})
// Streaming
const call = client.searchUsers({ query: 'Alice' })
call.on('data', user => console.log(user.name))
call.on('end', () => console.log('done'))javascriptSo Sánh API Trilogy#
| Tiêu chí | REST | GraphQL | gRPC |
|---|---|---|---|
| Format | JSON (text) | JSON (text) | Protobuf (binary) |
| Giao thức | HTTP/1.1 | HTTP/1.1 hoặc 2 | HTTP/2 |
| Schema | Không (hoặc OpenAPI) | Có (SDL) | Có (proto) bắt buộc |
| Code gen | Thủ công hoặc OpenAPI | Gen từ schema | Gen từ proto |
| Streaming | Server-Sent Events | Subscription | Bidirectional |
| Browser support | Native | Native | Cần grpc-web hoặc proxy |
| Payload size | Lớn (JSON) | Trung bình | Nhỏ (binary) |
| Tốc độ encode | Chậm | Trung bình | Nhanh |
| Học | Thấp | Trung bình | Cao (proto + HTTP/2) |
| Công cụ | curl, Postman | Playground | grpcurl, Postman |
Khi Nào Dùng Cái Nào?#
gRPC — internal microservices communication
- Service-to-service: latency sensitive, throughput cao
- Streaming data: real-time feed, chat, log shipping
- Polyglot environment: nhiều ngôn ngữ, cần code gen
- Ví dụ: Netflix, Uber, Spotify dùng gRPC cho internal traffic
REST — external/public API
- Browser/ mobile app gọi API
- Third-party integration
- Cần caching (HTTP cache)
- Developer familiarity (curl, Postman, mọi người đều biết)
GraphQL — complex data fetching
- Nhiều client khác nhau cần data khác nhau
- Frontend team muốn tự do select fields
- Dashboard, mobile app với nhiều màn hình
Pattern Phổ Biến#
[Browser/Mobile] → REST/GraphQL → [API Gateway] → gRPC → [Microservices]plaintextNetflix, Spotify, Mercari đều dùng pattern này. Bên ngoài là REST/GraphQL, bên trong là gRPC.
gRPC Trong Thực Tế#
Netflix — 10,000+ services nói chuyện với nhau qua gRPC, xử lý hàng tỷ request/ngày.
Spotify — thay thế in-house RPC framework Hermes bằng gRPC + Envoy, giảm độ phức tạp vận hành.
Uber — dùng gRPC cho toàn bộ internal traffic, với Envoy làm sidecar proxy.
Cloudflare — hỗ trợ gRPC over HTTP/3 (QUIC) trên CDN, giảm latency 47% trên mobile network.
Giới Hạn#
- Browser support: gRPC-Web cần proxy (Envoy) hoặc grpc-web client
- Không dùng curl: Cần grpcurl hoặc Postman
- Human-readable: Protobuf binary, cần tool để decode
- Load balancing: HTTP/2 multiplexing phải được hỗ trợ bởi LB
- HTTP/3: Còn experimental cho gRPC, chưa production-ready
Kết Luận#
REST và GraphQL không sai — nhưng chúng sinh ra cho client-server communication. gRPC sinh ra cho service-to-service.
Nếu bạn đang xây dựng hệ thống microservices, đặc biệt với nhiều ngôn ngữ khác nhau, gRPC là lựa chọn tối ưu: Protobuf nhỏ hơn JSON 10x, HTTP/2 multiplexing, bidirectional streaming, code gen tự động.
API Gateway nhận REST/GraphQL từ frontend, chuyển thành gRPC cho internal services — đó là kiến trúc đang chạy ở Netflix, Uber, Spotify, và hàng ngàn công ty khác.