What Is gRPC?
Remote Procedure Call
หัวข้อที่มีชื่อว่า “Remote Procedure Call”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 มาก่อน
หัวข้อที่มีชื่อว่า “contract มาก่อน”สิ่งที่ทำให้เรื่องนี้เป็นไปได้คือ 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 มาจากการประกาศชุดเดียวกัน
generate แล้วค่อย call
หัวข้อที่มีชื่อว่า “generate แล้วค่อย call”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
- เขียน
.protoแล้วรันprotoc(protobuf compiler) เพื่อ generate stub ที่มี type - implement server — เติมเนื้อในของ
GetUser - 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 parsingchannel = grpc.insecure_channel("localhost:50051")client = user_pb2_grpc.UserServiceStub(channel)
user = client.GetUser(user_pb2.GetUserRequest(id=42))print(user.name) # typed field, no JSON parsingconst client = new UserServiceClient( 'localhost:50051', credentials.createInsecure(),);
client.getUser({ id: 42 }, (err, user) => { if (err) throw err; console.log(user.name); // typed field, no JSON parsing});สิ่งที่ gRPC ให้คุณฟรี ๆ
หัวข้อที่มีชื่อว่า “สิ่งที่ gRPC ให้คุณฟรี ๆ”เพราะทุกอย่างไหลมาจาก 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 ไม่ใช่สิ่งที่คุณต้องคิดค้นใหม่