gRPC-Gateway & Transcoding
สองกลุ่มผู้ใช้ contract เดียว
หัวข้อที่มีชื่อว่า “สองกลุ่มผู้ใช้ contract เดียว”ในโมดูล foundations เราพูดไว้ว่าระบบเดียวกันมักอยากได้ gRPC ภายในและ REST ที่ขอบนอก — call ที่เร็วและ typed ระหว่าง service ของตัวเอง แต่เป็น JSON ธรรมดาสำหรับ third party, browser และการเช็คด้วย curl เร็ว ๆ
Transcoding ให้คุณ serve ทั้งสองจาก .proto เดียวกัน คุณ annotate แต่ละ method ด้วย REST route ที่ควรตอบด้วย แล้ว reverse proxy ที่ generate มาจะแปล request แบบ JSON/HTTP ที่เข้ามาให้เป็น gRPC call และแปลง protobuf response กลับเป็น JSON
Annotate ที่ method
หัวข้อที่มีชื่อว่า “Annotate ที่ method”คุณอธิบาย REST mapping ไว้ใน service definition เลย โดยใช้ google.api.http annotation:
import "google/api/annotations.proto";
service UserService { rpc GetUser(GetUserRequest) returns (User) { option (google.api.http) = { get: "/v1/users/{id}" }; }
rpc CreateUser(CreateUserRequest) returns (User) { option (google.api.http) = { post: "/v1/users" body: "*" }; }}ตอนนี้ GetUser ตอบ GET /v1/users/42 ได้ด้วย — path segment {id} map เข้ากับ field id ของ request ส่วน CreateUser ตอบ POST /v1/users โดย JSON body map เข้ากับ request message ตัว gRPC method ไม่เปลี่ยน annotation แค่เพิ่มประตู REST เข้าไป
.proto เดียว สองประตูหน้าบ้าน
หัวข้อที่มีชื่อว่า “.proto เดียว สองประตูหน้าบ้าน”flowchart TB proto[".proto + google.api.http annotations"] --> svc["UserService (gRPC impl)"] internal["internal services (native gRPC)"] --> svc ext["external client GET /v1/users/42"] --> gw["grpc-gateway reverse proxy"] gw -->|translates JSON↔protobuf| svc
gateway เป็น code ที่ generate มา (grpc-gateway ใน ecosystem ของ Go; Envoy ก็ทำ transcoding ผ่าน filter ได้) ตัว gateway จะ parse URL กับ JSON, สร้าง protobuf request, เรียก gRPC method จริง แล้ว marshal reply กลับเป็น JSON — ทั้งหมดขับเคลื่อนด้วย annotation
// Run the gateway alongside the gRPC servermux := runtime.NewServeMux()err := userv1.RegisterUserServiceHandlerFromEndpoint( ctx, mux, "localhost:50051", []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())},)if err != nil { log.Fatal(err) }http.ListenAndServe(":8080", mux) // REST/JSON on :8080, gRPC on :50051# Python typically uses Envoy's grpc_json_transcoder filter,# pointed at the same descriptor set the .proto compiles to:# typed_config:# proto_descriptor: "user_descriptor.pb"# services: ["user.v1.UserService"]// Node commonly fronts gRPC with Envoy transcoding as well,// or uses Connect, which can serve JSON directly from the same service.// Either way the mapping lives in the .proto annotations, not hand-written routes.เมื่อไรควรหยิบมาใช้
หัวข้อที่มีชื่อว่า “เมื่อไรควรหยิบมาใช้”- มี consumer ภายนอกหรือ browser ที่คาดหวัง REST/JSON แต่คุณไม่อยาก maintain API ตัวที่สองด้วยมือ transcoding ทำให้เหลือ contract เดียว
- อยากได้ความ
curl-ได้ และ OpenAPI doc annotation ชุดเดียวกัน generate OpenAPI spec ได้ - ข้ามไปถ้าทุกอย่างอยู่ภายใน ถ้ามีแต่ service ของตัวเองที่เรียก API การเพิ่ม proxy hop และการแปลง JSON เป็น overhead ล้วน ๆ — อยู่กับ native gRPC ต่อไป