Bỏ qua menu, vào nội dung chính

gRPC cơ bản: khi nào nên dùng thay REST

GRPC cơ bản: protocol buffers, streaming, code Node.js minh hoạ. So sánh với REST, khi nào nên dùng gRPC cho microservice nội bộ.

3 phút đọc

gRPC cơ bản là kỹ năng cần thiết khi làm microservice nội bộ. Bài này giới thiệu protocol buffers, 4 mode streaming, kèm code Node.js minh hoạ - và quan trọng hơn: khi nào nên dùng gRPC, khi nào REST đủ.

Trang giới thiệu gRPC trên grpc.io
Trang giới thiệu gRPC trên grpc.io (grpc.io), ảnh chụp ngày 27/09/2026.

gRPC là gì?

gRPC = Google Remote Procedure Call. Framework RPC mở do Google phát hành 2015, dựa trên:

  • Protocol Buffers (protobuf): binary format có schema
  • HTTP/2: multiplexing, server push, header compression
  • Code generation: từ.proto file -> client + server code cho 11+ ngôn ngữ

So với REST, gRPC tập trung vào: tốc độ, contract chặt, streaming. Đánh đổi: phức tạp hơn, browser không support native.

.proto file: schema-first design

protobuf// users.proto
syntax = "proto3";

package alodev.users.v1;

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
  rpc StreamUserUpdates (StreamRequest) returns (stream UserUpdate);
}

message User {
  int64 id = 1;
  string email = 2;
  string name = 3;
  Role role = 4;
  google.protobuf.Timestamp created_at = 5;
}

enum Role {
  ROLE_UNSPECIFIED = 0;
  ROLE_USER = 1;
  ROLE_ADMIN = 2;
}

message GetUserRequest {
  int64 id = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
}

Mỗi field có tag number (1, 2, 3...) - đây là khoá để binary encoding biết field nào là gì. Tag không bao giờ reuse - đây là rule cho backwards compat.

Implement server Node.js

bashnpm install @grpc/grpc-js @grpc/proto-loader
javascript// server.js
import grpc from '@grpc/grpc-js'
import protoLoader from '@grpc/proto-loader'

const def = protoLoader.loadSync('./users.proto', {
  keepCase: true, longs: String, enums: String, defaults: true,
})
const proto = grpc.loadPackageDefinition(def).alodev.users.v1

const userImpl = {
  GetUser: async (call, callback) => {
    const user = await db.users.findById(call.request.id)
    if (!user) {
      return callback({
        code: grpc.status.NOT_FOUND,
        message: `User ${call.request.id} không tồn tại`,
      })
    }
    callback(null, {
      id: user.id,
      email: user.email,
      name: user.name,
      role: user.role,
      created_at: { seconds: Math.floor(user.created_at.getTime() / 1000) },
    })
  },

  ListUsers: async (call, callback) => {
    const limit = call.request.page_size || 50
    const cursor = call.request.page_token ? parseInt(call.request.page_token) : 0
    const rows = await db.users.list({ offset: cursor, limit })
    callback(null, {
      users: rows,
      next_page_token: rows.length === limit ? String(cursor + limit) : '',
    })
  },

  // Server streaming - push user updates real-time
  StreamUserUpdates: (call) => {
    const interval = setInterval(async () => {
      const update = await getNextUpdate()
      if (update) call.write(update)
    }, 1000)
    call.on('end', () => clearInterval(interval))
  },
}

const server = new grpc.Server()
server.addService(proto.UserService.service, userImpl)
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
  console.log('gRPC server :50051')
})

Implement client Node.js

javascript// client.js
import grpc from '@grpc/grpc-js'
import protoLoader from '@grpc/proto-loader'

const def = protoLoader.loadSync('./users.proto', { /* ... */ })
const proto = grpc.loadPackageDefinition(def).alodev.users.v1

const client = new proto.UserService(
  'localhost:50051',
  grpc.credentials.createInsecure()
)

// Unary call
client.GetUser({ id: 42 }, (err, user) => {
  if (err) {
    if (err.code === grpc.status.NOT_FOUND) console.log('Not found')
    else throw err
  }
  console.log(user)
})

// Server streaming
const stream = client.StreamUserUpdates({})
stream.on('data', (update) => console.log('update:', update))
stream.on('error', (err) => console.error(err))
stream.on('end', () => console.log('stream ended'))

4 mode streaming

text1. Unary:           client → server (1 req, 1 resp). Như REST.
2. Server stream:   client → server (1 req, N resp). Push notification.
3. Client stream:   client → server (N req, 1 resp). Upload file.
4. Bidirectional:   client ⇄ server (N req, N resp). Chat, real-time.

Đây là điểm mạnh lớn so với REST - REST chỉ unary native. SSE giải được server stream nhưng không có chiều ngược.

Khi nào dùng gRPC?

Dùng gRPC khi:

  • Service-to-service nội bộ - không browser facing
  • Latency-sensitive - payment, ad serving, real-time bidding
  • Streaming là requirement (server push, bidirectional)
  • Multi-language: Go service gọi Python service gọi Java service
  • Schema strict bắt buộc (PCI, fintech)

Không nên gRPC khi:

  • Public API browser-facing
  • Tooling đơn giản quan trọng (REST + Postman)
  • Team mới - learning curve cao
  • Caching HTTP cần thiết (gRPC không cache HTTP)
  • Debug bằng curl/log dễ - protobuf binary khó đọc

gRPC vs REST so sánh nhanh

Khía cạnhgRPCREST
FormatBinary (protobuf)Text (JSON)
TransportHTTP/2HTTP/1.1 hoặc 2
Schema.proto bắt buộcOpenAPI optional
Streaming4 mode nativeSSE / WebSocket
BrowserCần gRPC-Web proxyNative
ToolingBloomRPC, grpcurlPostman, curl
Code genBuilt-in cho 11+ langOpenAPI codegen
Learning curveCaoThấp

Kết luận

gRPC cơ bản dễ học - phức tạp ở tooling và operational. Nếu anh đang build microservice nội bộ với latency yêu cầu < 50ms, gRPC là lựa chọn xuất sắc. Public API hoặc team mới thì REST vẫn là default an toàn. Đọc thêm Microservices vs Monolith để hiểu khi nào việc tách service xứng đáng đầu tư gRPC.

Nhận bài mới

Để lại email để nhận bài mới của series Kiến thức lập trình, hoặc theo dõi bằng RSS.

RSS Kiến thức lập trình

Email chỉ dùng để gửi bài mới, xem Chính sách bảo mật.

Trao đổi về dự án

Cần tư vấn?