Defining Services
service ประกาศด้วย keyword service และแต่ละ method ด้วย rpc รูปแบบเหมือนกันเสมอ: ชื่อ, request message หนึ่งตัวในวงเล็บ, และ returns พร้อม response message หนึ่งตัว
syntax = "proto3";package user.v1;
service UserService { rpc GetUser(GetUserRequest) returns (User); rpc CreateUser(CreateUserRequest) returns (User); rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);}อ่านไฟล์นี้เหมือนเมนูของ call: service นี้มี 3 method แต่ละตัวมี input และ output ที่มี type ชัดเจน นั่นคือหน้าตา API ทั้งหมด — ไม่มี URL, ไม่มี verb, ไม่มี query string
หนึ่ง request และหนึ่ง response message ต่อ method
หัวข้อที่มีชื่อว่า “หนึ่ง request และหนึ่ง response message ต่อ method”สังเกตว่าทุก method รับ request message ของตัวเอง และ return response message ของตัวเอง — แม้แต่ GetUser ที่ในเชิงแนวคิดต้องการแค่ id นี่ตั้งใจทำ ไม่ใช่เขียนยาวเกินจำเป็น
// ทำแบบนี้ — message เฉพาะตัวrpc GetUser(GetUserRequest) returns (User);
message GetUserRequest { int64 id = 1;}ถ้าพรุ่งนี้คุณต้องเพิ่ม “รวม user ที่ถูกลบด้วย” หรือ “เอา field ไหนกลับมาบ้าง” คุณแค่เพิ่ม field เข้าไปใน GetUserRequest — เป็นการเปลี่ยนที่ backward-compatible และปลอดภัย ถ้า method รับ int64 เปล่า ๆ คุณจะไม่มีที่ให้ขยาย และการเปลี่ยน signature จะทำให้ทุก client พัง wrapper message ให้พื้นที่ทุก method ได้ evolve บทเรียน Designing Methods จะกลับมาที่กฎนี้แบบละเอียด
Package และการตั้งชื่อ
หัวข้อที่มีชื่อว่า “Package และการตั้งชื่อ”การประกาศ package ทำ namespace ให้ type ของคุณเพื่อไม่ให้ชนกับ type จาก .proto ไฟล์อื่น และยัง map ไปเป็น namespace ใน code ที่ generate ออกมาด้วย (Go package, Python module prefix และอื่น ๆ)
package user.v1;มี 2 convention ที่สำคัญ:
- ชื่อแบบ reverse-domain หรืออิงกับ product ทำให้ package unique ทั่วโลก:
acme.user.v1,payments.billing.v2 - ชื่อ service ใช้ PascalCase (
UserService), method ใช้ PascalCase (GetUser), และ field ใช้ snake_case (user_id) ตัว code generator จะแปลงเป็นสำนวนของแต่ละภาษาให้อัตโนมัติ
ใส่ version ไว้ใน package
หัวข้อที่มีชื่อว่า “ใส่ version ไว้ใน package”นี่คือนิสัยการออกแบบที่สำคัญที่สุดในบทเรียนนี้: API version อยู่ใน package path เป็น v1, v2 และต่อ ๆ ไป
flowchart TB v1["package user.v1 UserService"] --> old["existing clients keep calling v1"] v2["package user.v2 UserService (redesigned)"] --> new["new clients adopt v2"] note["both run side by side until v1 is retired"]
เพราะ user.v1.UserService และ user.v2.UserService เป็น type คนละตัวที่มีตัวตนบน wire ต่างกัน คุณจึง ship การ redesign แบบ breaking เป็น v2 ได้ ส่วน v1 ยังให้บริการ client เดิมต่อไป ไม่ต้องมี flag-day migration ที่บังคับให้ย้ายพร้อมกัน พอ client ทุกตัวย้ายครบแล้วค่อยปลด v1 ทิ้ง ใส่ version ไว้ตั้งแต่วันแรก — ไปเติมทีหลังจะเจ็บปวด