Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 15 — gRPC & Protocol Buffers

Technical BA Masterclass Bài 15/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là Technical BA của một ngân hàng số tại Việt Nam. Đội kiến trúc đưa cho bạn một tài liệu thiết kế hệ thống thanh toán mới, và trong sơ đồ có một mũi tên ghi chú lạ lẫm: "gRPC over HTTP/2, Protobuf v3". Đồng nghiệp bên dev coi đó là chuyện đương nhiên, còn bạn — người sẽ viết spec, làm việc với đối tác tích hợp, và giải thích cho phía nghiệp vụ — lại thấy mơ hồ. Nếu không hiểu gRPC là gì, vì sao đội kỹ thuật chọn nó thay vì REST quen thuộc, bạn sẽ rất khó viết được một tài liệu integration chính xác, và càng khó để thẩm định khi đối tác bảo "API này không gọi bằng Postman thông thường được đâu".

Trong các bài trước về REST, OpenAPI, GraphQL, bạn đã quen với thế giới API "con người đọc được" — JSON, URL, phương thức GET/POST. gRPC thuộc một thế giới khác: nó được sinh ra để máy nói chuyện với máy thật nhanh, thật chặt, trong nội bộ hệ thống. Càng lên các dự án lớn — microservices, core banking, hệ thống real-time — bạn càng gặp gRPC nhiều. Là Technical BA, bạn không cần viết code gRPC, nhưng bạn bắt buộc phải hiểu nó vận hành thế nào, đọc được file định nghĩa hợp đồng (contract), và biết khi nào nên khuyến nghị dùng nó. Đó chính là nội dung của bài này.

Khái niệm cốt lõi

gRPC là gì?

gRPC là một framework gọi hàm từ xa (Remote Procedure Call — RPC) do Google phát triển và mở mã nguồn năm 2015. Ý tưởng cốt lõi của RPC rất tự nhiên: thay vì bạn phải nghĩ theo kiểu "gửi một request HTTP tới URL /users/123", bạn gọi một hàm như thể nó nằm ngay trong code của mình — getUser(123) — và framework lo phần truyền dữ liệu qua mạng giúp bạn. Service ở máy khác thực thi hàm đó rồi trả kết quả về. Với lập trình viên, cảm giác như gọi một hàm cục bộ, dù thực tế nó chạy ở một máy chủ cách đó hàng nghìn cây số.

Chữ "g" thường được đùa là "Google", nhưng mỗi phiên bản gRPC lại gán cho nó một ý nghĩa khác nhau (good, green, glorious...). Điều quan trọng hơn là hai nền tảng kỹ thuật làm nên sức mạnh của gRPC: Protocol Buffers để mã hóa dữ liệu, và HTTP/2 để truyền tải.

Protocol Buffers — trái tim của gRPC

Protocol Buffers (thường gọi tắt là Protobuf) là định dạng tuần tự hóa dữ liệu (serialization format) do Google tạo ra. Khác với JSON là văn bản (text) mà con người đọc được, Protobuf mã hóa dữ liệu thành dạng nhị phân (binary) — gọn hơn, nhẹ hơn, và máy đọc nhanh hơn nhiều.

Điểm mấu chốt với BA là: Protobuf đòi hỏi bạn định nghĩa trước cấu trúc dữ liệu trong một file .proto. File này chính là hợp đồng (contract) giữa hai bên. Ví dụ một file .proto đơn giản trông như sau:

syntax = "proto3";

service AccountService { rpc GetBalance (BalanceRequest) returns (BalanceResponse); }

message BalanceRequest { string account_id = 1; }

message BalanceResponse { string account_id = 1; int64 balance = 2; string currency = 3; }

Đọc đoạn này như một BA, bạn thấy ngay: có một service tên AccountService, cung cấp một thao tác GetBalance, nhận vào BalanceRequest (chứa account_id) và trả về BalanceResponse (có số dư, loại tiền tệ). Những con số = 1, = 2 không phải giá trị, mà là field number — số thứ tự định danh từng trường khi mã hóa nhị phân. Ghi nhớ chi tiết này, vì nó liên quan trực tiếp đến việc quản lý tương thích ngược (backward compatibility) mà ta sẽ bàn sau.

So với việc viết một tài liệu API bằng văn xuôi, file .proto là hợp đồng cực kỳ chặt chẽ: cả hai bên đều dùng chung file này, dùng công cụ tự sinh ra code cho ngôn ngữ của mình (Java, Go, Python, C#...). Nhờ vậy không còn cảnh "spec ghi field là accountId mà code lại trả account_id".

HTTP/2 — đường truyền tốc độ cao

gRPC chạy trên HTTP/2 chứ không phải HTTP/1.1 như REST truyền thống. HTTP/2 mang lại vài lợi thế lớn: cho phép truyền nhiều luồng dữ liệu song song trên cùng một kết nối (multiplexing), nén header, và quan trọng nhất là hỗ trợ streaming hai chiều. Đây là lý do gRPC có thể làm những việc REST làm rất vất vả.

Bốn kiểu giao tiếp của gRPC

Đây là phần BA cần nắm để mô tả luồng nghiệp vụ chính xác:

  • Unary: một request, một response. Giống hệt REST thông thường. Ví dụ: tra cứu số dư.
  • Server streaming: client gửi một request, server trả về một luồng nhiều response liên tục. Ví dụ: đăng ký nhận cập nhật giá chứng khoán theo thời gian thực.
  • Client streaming: client gửi một luồng nhiều message, server trả về một response duy nhất. Ví dụ: tải lên hàng loạt giao dịch rồi nhận kết quả tổng hợp.
  • Bidirectional streaming: cả hai bên cùng gửi luồng dữ liệu qua lại đồng thời. Ví dụ: hệ thống chat, hoặc luồng dữ liệu định vị xe real-time.

Khi nào nên dùng gRPC?

Câu hỏi này là phần giá trị nhất mà một Technical BA mang lại. gRPC tỏa sáng khi:

  • Giao tiếp nội bộ giữa các microservices (internal service-to-service), nơi tốc độ và hiệu năng quan trọng hơn việc con người đọc được payload.
  • Cần độ trễ thấp và throughput cao — hệ thống xử lý hàng chục nghìn request mỗi giây.
  • Cần streaming real-time hai chiều.
  • Môi trường đa ngôn ngữ lập trình, cần hợp đồng chặt để các đội khác nhau không hiểu lầm.
Ngược lại, gRPC không phù hợp khi: bạn cần expose API cho trình duyệt web trực tiếp (browser hỗ trợ gRPC hạn chế, phải qua gRPC-Web), cho đối tác bên ngoài muốn test bằng Postman/curl dễ dàng, hoặc cho các API công khai mà tính dễ đọc, dễ debug quan trọng hơn tốc độ.

Tình huống thực tế

Tình huống 1: Fintech chuyển từ REST sang gRPC nội bộ

Một công ty fintech tại TP.HCM (giả định tên Velo Pay) vận hành ví điện tử với khoảng 2 triệu người dùng. Hệ thống của họ được tách thành nhiều microservices: dịch vụ ví, dịch vụ thanh toán, dịch vụ chống gian lận (fraud), dịch vụ thông báo. Ban đầu các service này gọi nhau bằng REST/JSON. Khi lưu lượng tăng vào giờ cao điểm (ví dụ các đợt flash sale của đối tác thương mại điện tử), đội kỹ thuật phát hiện độ trễ giữa các service tăng vọt, đặc biệt là ở dịch vụ fraud — nơi mỗi giao dịch phải gọi qua lại nhiều lần.

Đội kiến trúc quyết định chuyển giao tiếp nội bộ giữa dịch vụ thanh toán và dịch vụ fraud sang gRPC. Là Technical BA, bạn được giao viết tài liệu integration mô tả luồng mới. Sau khi triển khai thử nghiệm, kết quả đo được: payload mỗi lời gọi giảm khoảng 60% kích thước (từ JSON ~1.2 KB xuống Protobuf ~480 byte), và độ trễ trung bình (p99 latency) giảm từ 45ms xuống còn khoảng 18ms cho mỗi lần kiểm tra fraud.

Bài học rút ra: gRPC không thay thế toàn bộ REST. Velo Pay vẫn giữ REST cho API mà app di động gọi vào (vì cần tương thích rộng và dễ debug), chỉ dùng gRPC ở các luồng nội bộ tốc độ cao. Là BA, bạn cần vẽ rõ ranh giới này trong sơ đồ kiến trúc: "edge" (giáp người dùng) dùng REST, "internal mesh" dùng gRPC.

Tình huống 2: Hệ thống định vị xe real-time cho logistics

Một startup logistics ở khu vực Đông Nam Á (giả định tên RoadLink, hoạt động tại Việt Nam và Indonesia) cần theo dõi vị trí của 8.000 xe tải theo thời gian thực. Mỗi xe gửi tọa độ GPS mỗi 2 giây. Trung tâm điều phối cần nhận luồng cập nhật liên tục và đồng thời gửi lệnh điều phối ngược lại cho tài xế.

Đây chính là bài toán mà REST rất khó giải quyết gọn gàng — vì REST về bản chất là request/response một chiều, nếu muốn cập nhật liên tục thì phải polling (hỏi đi hỏi lại) hoặc dựng WebSocket riêng. Đội kỹ thuật RoadLink chọn bidirectional streaming của gRPC: mỗi xe duy trì một kết nối streaming, vừa đẩy vị trí lên vừa nhận lệnh điều phối về trên cùng một luồng.

Khi viết spec, BA của RoadLink mô tả rõ trong file .proto: một RPC TrackVehicle kiểu stream LocationUpdate đi vào và stream DispatchCommand đi ra. Tài liệu nêu rõ tần suất gửi, hành vi khi mất kết nối (reconnect), và giới hạn timeout.

Bài học rút ra: khi nghiệp vụ yêu cầu dữ liệu real-time hai chiều liên tục, gRPC streaming là lựa chọn tự nhiên. BA cần nhận diện đặc điểm "real-time, hai chiều, nhiều kết nối song song" để gợi ý đúng công nghệ, thay vì mặc định đề xuất REST polling vốn tốn tài nguyên và trễ.

Tình huống 3: Bài học đau về backward compatibility

Một ngân hàng tại Hà Nội triển khai gRPC giữa hệ thống core và một service mới về quản lý hạn mức tín dụng. Một lập trình viên trẻ, khi cần thêm trường mới vào message, đã chèn trường đó vào giữa và đánh lại số field — đổi field currency = 3 thành currency = 4, dùng số 3 cho trường mới risk_score. Sau khi deploy service mới mà chưa deploy đồng bộ phía đối tác, hệ thống bắt đầu trả về dữ liệu sai: số dư bị đọc nhầm thành mã rủi ro, một vài báo cáo hiển thị con số vô lý.

Nguyên nhân: trong Protobuf, field number chính là khóa định danh khi mã hóa nhị phân, không phải tên trường. Đổi số field đồng nghĩa với phá vỡ hợp đồng. Bên gửi mã hóa currency ở số 4, bên nhận cũ vẫn nghĩ số 3 là currency — kết quả là dữ liệu lệch hoàn toàn nhưng hệ thống không báo lỗi rõ ràng vì binary vẫn "hợp lệ".

Bài học rút ra: nguyên tắc vàng của Protobuf — không bao giờ thay đổi hoặc tái sử dụng field number đã phát hành. Muốn thêm trường, hãy gán số mới chưa dùng. Muốn bỏ trường, hãy đánh dấu reserved để không ai tái dùng số đó. Là BA viết spec cho thay đổi schema, bạn phải ghi rõ quy tắc này như một mục bắt buộc trong tài liệu, và đưa nó vào checklist review thay đổi.

Hướng dẫn từng bước

Khi bạn là Technical BA cần làm việc với một tính năng có dùng gRPC, hãy đi theo các bước sau:

  • Xác định ranh giới giao tiếp: Vẽ sơ đồ các service và chỉ rõ đâu là giao tiếp nội bộ (ứng viên cho gRPC) và đâu là giao tiếp với bên ngoài / trình duyệt (thường vẫn REST). Đừng đề xuất gRPC cho API mà đối tác bên ngoài hay app web cần gọi trực tiếp.
  • Đọc và hiểu file .proto: Yêu cầu đội dev cung cấp file .proto. Tập trung vào ba thứ — tên service, các RPC (mỗi RPC là một thao tác nghiệp vụ), và cấu trúc message (đầu vào, đầu ra). Đối chiếu từng RPC với một use case nghiệp vụ cụ thể.
  • Xác định kiểu giao tiếp cho từng RPC: Với mỗi thao tác, ghi rõ nó là unary, server streaming, client streaming hay bidirectional. Điều này ảnh hưởng trực tiếp tới cách bạn mô tả luồng và các yêu cầu phi chức năng (timeout, reconnect).
  • Đặc tả các trường hợp lỗi và mã trạng thái: gRPC có bộ status code riêng (như NOT_FOUND, INVALID_ARGUMENT, DEADLINE_EXCEEDED, UNAVAILABLE) thay cho mã HTTP 404/400. Trong spec, ánh xạ mỗi tình huống nghiệp vụ lỗi sang status code tương ứng.
  • Định nghĩa quy tắc quản lý phiên bản schema: Ghi rõ quy ước về field number (không đổi, không tái dùng, dùng reserved khi xóa), và quy trình review khi thay đổi file .proto.
  • Làm rõ yêu cầu phi chức năng: deadline/timeout cho mỗi lời gọi, hành vi retry, giới hạn kích thước message, cơ chế xác thực (thường qua metadata/token). Phối hợp với kiến trúc sư để con số này hợp lý.
  • Lên kế hoạch kiểm thử: Vì gRPC không test bằng trình duyệt được, hãy ghi chú công cụ phù hợp như grpcurl, Postman (bản mới đã hỗ trợ gRPC), hoặc BloomRPC, để QA và đối tác biết cách kiểm thử.

Lỗi thường gặp & mẹo

  • Nhầm gRPC là "REST nhanh hơn": gRPC khác về bản chất — RPC chứ không phải resource-oriented. Đừng cố ép mô hình URL/resource của REST vào gRPC trong tài liệu. Hãy mô tả theo thao tác (operation).
  • Đề xuất gRPC cho API public hoặc cho trình duyệt: Đây là lỗi phổ biến. Trình duyệt không gọi gRPC trực tiếp được, phải dùng gRPC-Web kèm proxy. Với API công khai cho đối tác, REST/JSON vẫn dễ tiếp cận hơn nhiều. Mẹo: hỏi "ai là người gọi API này?" trước khi chọn.
  • Bỏ qua quy tắc field number: Như tình huống 3, đây là lỗi nghiêm trọng và âm thầm. Mẹo: đưa quy tắc "không sửa/không tái dùng field number, dùng reserved khi xóa" thành một dòng cố định trong mọi spec liên quan đến .proto.
  • Quên đặc tả deadline/timeout: gRPC khuyến khích mỗi lời gọi đều có deadline. Nếu spec không nói, hệ thống dễ bị treo khi service phụ thuộc chậm. Mẹo: luôn có một mục "Deadline mặc định" trong tài liệu integration.
  • Cho rằng binary nghĩa là bảo mật: Protobuf là nhị phân nên khó đọc bằng mắt, nhưng đó không phải mã hóa. Dữ liệu vẫn cần TLS để bảo mật đường truyền. Đừng nhầm "khó đọc" với "an toàn".
  • Mẹo giao tiếp với nghiệp vụ: Khi giải thích cho người không kỹ thuật, hãy dùng phép so sánh — REST giống như gửi thư có địa chỉ rõ ràng ai cũng đọc được, còn gRPC giống như hai máy bộ đàm đã thống nhất sẵn một bộ mã, nói chuyện cực nhanh nhưng người ngoài nghe vào không hiểu.

Bài tập thực hành

  • Đọc hiểu hợp đồng: Lấy file .proto mẫu trong bài (service AccountService). Viết một đoạn mô tả nghiệp vụ bằng tiếng Việt cho RPC GetBalance: đầu vào là gì, đầu ra là gì, và một use case thực tế sử dụng nó.
  • Phân loại kiểu giao tiếp: Cho ba nghiệp vụ sau, hãy xác định mỗi nghiệp vụ nên dùng kiểu gRPC nào (unary / server streaming / client streaming / bidirectional) và giải thích: (a) tra cứu thông tin khách hàng theo CMND; (b) nhận thông báo biến động số dư real-time; (c) tải lên một batch 10.000 giao dịch cuối ngày để đối soát.
  • Quyết định công nghệ: Một sàn thương mại điện tử cần (a) một API cho app di động lấy danh sách sản phẩm, và (b) giao tiếp nội bộ giữa service giỏ hàng và service kho để kiểm tra tồn kho tốc độ cao. Với mỗi trường hợp, bạn khuyến nghị REST hay gRPC? Viết 3–4 câu lập luận cho mỗi lựa chọn.
  • Phát hiện rủi ro schema: Đội dev đề xuất xóa trường old_currency = 2 và thêm trường new_currency dùng lại số 2. Hãy viết phản hồi của bạn với tư cách BA, chỉ ra rủi ro và đề xuất cách làm đúng.

Tóm tắt

gRPC là framework gọi hàm từ xa do Google phát triển, dùng Protocol Buffers (mã hóa nhị phân, nhanh và gọn) làm hợp đồng dữ liệu và HTTP/2 làm đường truyền tốc độ cao có hỗ trợ streaming hai chiều. Sức mạnh của nó nằm ở hiệu năng và hợp đồng chặt chẽ qua file .proto, nên nó tỏa sáng trong giao tiếp nội bộ giữa các microservices, hệ thống real-time, và môi trường đa ngôn ngữ lập trình — nhưng không phù hợp cho API public hay gọi trực tiếp từ trình duyệt.

Là Technical BA, bạn không viết code gRPC, nhưng bạn cần: đọc hiểu file .proto như một hợp đồng nghiệp vụ, phân biệt bốn kiểu giao tiếp để mô tả luồng chính xác, nắm vững quy tắc bất biến về field number để bảo vệ tương thích ngược, và đặc tả đầy đủ các yêu cầu phi chức năng như deadline, retry, xác thực. Quan trọng nhất, giá trị lớn nhất bạn mang lại là trả lời được câu hỏi "khi nào dùng gRPC, khi nào dùng REST" một cách thuyết phục — và biết vẽ rõ ranh giới đó trong kiến trúc hệ thống.