ข้ามไปยังเนื้อหา

What Is gRPC?

gRPC ย่อมาจาก gRPC Remote Procedure Call (ใช่ เป็น recursive) ส่วนที่สำคัญคือ RPC: แนวคิดว่าการเรียก code บนอีกเครื่องหนึ่งควรจะดูเหมือนการเรียก function ปกติ

ถ้าไม่มี RPC การเรียกกันระหว่าง service หมายถึง: สร้าง URL, เลือก verb, serialize body เป็น JSON, ส่ง HTTP request, เช็ค status code, parse response และภาวนาว่าทั้งสองฝั่งตกลงเรื่องรูปร่างข้อมูลตรงกัน RPC ยุบทั้งหมดนั้นให้เหลือบรรทัดเดียว:

user = userService.GetUser(id: 42)

คุณเรียก method คุณได้ผลลัพธ์ที่มี type ส่วน transport, serialization และ error handling ถูก generate ให้และซ่อนอยู่หลัง call นั้น

สิ่งที่ทำให้เรื่องนี้เป็นไปได้คือ contract ที่เขียนก่อนจะ implement อะไรทั้งนั้น — ไฟล์ .proto ประกาศ message (ข้อมูล) และ service (method):

syntax = "proto3";
package user.v1;
message GetUserRequest {
int64 id = 1;
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
}

ไฟล์นี้คือ single source of truth และเป็นกลางไม่ผูกกับภาษา: .proto ตัวเดียวกันสร้าง Go server, Python client และ Node service ที่คุยกันได้อย่างสมบูรณ์ เพราะทั้งหมด generate มาจากการประกาศชุดเดียวกัน

workflow มี 3 ขั้น และมีแค่ขั้นกลางเท่านั้นที่เป็น code ที่คุณเขียนเอง:

flowchart LR
  proto[".proto contract"] -->|protoc| stubs["generated stubs
(client + server)"]
  stubs --> impl["คุณ implement
method ฝั่ง server"]
  impl --> run["client เรียก
stub.GetUser(42)"]
  run -->|HTTP/2 + protobuf| server["server รัน
GetUser, คืน User"]
  server --> run
จาก contract ไปสู่ call ที่รันได้จริง
  1. เขียน .proto แล้วรัน protoc (protobuf compiler) เพื่อ generate stub ที่มี type
  2. implement server — เติมเนื้อในของ GetUser
  3. call จาก client — เรียก method ของ stub ที่ generate มา; request จะถูก serialize เป็น protobuf, ส่งผ่าน HTTP/2 แล้ว response ที่มี type ก็กลับมา

นี่คือ call ฝั่ง client ใน 3 ภาษา สังเกตว่าสั้นแค่ไหน: ไม่มี URL, ไม่มี JSON, ไม่ต้องเช็ค status code — แค่เรียก method

conn, _ := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
defer conn.Close()
client := userv1.NewUserServiceClient(conn)
user, err := client.GetUser(ctx, &userv1.GetUserRequest{Id: 42})
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name) // typed field, no JSON parsing

เพราะทุกอย่างไหลมาจาก contract framework จึงยื่นอะไรให้คุณได้เยอะ:

  • type safety ข้าม wire — client กับ server ขัดแย้งกันเรื่องชื่อ field หรือ type ไม่ได้ เพราะ generate มาจาก schema เดียวกัน
  • binary encoding ที่มีประสิทธิภาพ — protobuf เล็กกว่าและ parse เร็วกว่า JSON
  • streaming — โมเดลเดียวกันขยายไปสู่การส่ง message จำนวนมากได้ทั้งสองทิศทาง (มีทั้งโมดูลรออยู่ข้างหน้า)
  • interop ข้ามภาษา — ภาษาทางการ 11+ ภาษา คุยกันด้วย protocol เดียวกัน
  • feature ที่มีมาให้ในตัว — deadline, cancellation, metadata, interceptor และ load balancing มากับ framework ไม่ใช่สิ่งที่คุณต้องคิดค้นใหม่
คำว่า "RPC" ใน gRPC มีความหมายอย่างไรต่อวิธีที่คุณเขียน code?
ใน workflow ของ gRPC อะไรคือ single source of truth?
ทำไม Go server กับ Python client ถึงคุยกันได้อย่างสมบูรณ์ใน gRPC?
ข้อไหนที่ gRPC ให้คุณโดยไม่ต้องทำงานเพิ่ม?