Bỏ qua đến nội dung chính
gRPCprotocol buffersmicroservicesRPCbackend

gRPC Cơ Bản: Khi Nào Nên Dùng Thay Cho 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ộ.

Xuất bản 8 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 đủ.

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

// 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

npm install @grpc/grpc-js @grpc/proto-loader
// 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

// 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

1. 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.

Zalo