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

Versioning & Evolution

โมดูล protobuf แสดงให้เห็นว่า wire format ทนการเปลี่ยนแปลงได้อย่างไร — field number ไม่ใช่ name เป็นตัวระบุข้อมูล บทเรียนนี้พูดถึงวินัยที่เปลี่ยนความทนระดับล่างนั้นให้กลายเป็น versioning strategy จริงที่ caller พึ่งพาได้

กฎทอง: client เก่ากับ server ใหม่ต้องยังทำงานได้ และ client ใหม่กับ server เก่าต้องยังทำงานได้ ทุกอย่างข้างล่างนี้เพื่อเป้าหมายนั้น

convention คือใส่ major version ไว้ใน protobuf package (และใน path ของไฟล์):

package user.v1; // user/v1/user.proto

เมื่อต้องเปลี่ยนแบบ incompatible คุณไม่แก้ v1 แต่สร้าง user.v2 ขึ้นมาคู่กัน code ที่ generate ลงคนละ namespace ทำให้ v1 กับ v2 รันใน server เดียวกันพร้อมกันได้ และ caller ค่อย migrate ตามตารางของตัวเอง

การเปลี่ยนส่วนใหญ่ควรเป็นแบบ additive — ปลอดภัยเพราะ wire format ไม่สนสิ่งที่ไม่รู้จัก:

ปลอดภัย (additive)breaking (ต้องขึ้น version ใหม่)
เพิ่ม field ใหม่ด้วย number ใหม่เปลี่ยน number ของ field
เพิ่ม method ใหม่เข้า serviceเปลี่ยน type ของ field
เพิ่ม enum value ใหม่ (ปกติ)rename/reuse number ของ field ที่ลบไปแล้ว
เพิ่ม message ใหม่ลบ field ที่ยังใช้อยู่
rename package, service หรือ method
flowchart TB
  v1["user.v1"] -->|add field #7,
add method| v1b["user.v1
(still compatible)"]
  v1 -->|change field type,
remove field| v2["user.v2
(new package,
runs alongside v1)"]
  client1["old client"] --> v1b
  client2["new client"] --> v2
additive change อยู่ใน v1 ต่อไป ส่วน incompatible change ได้ v2

เมื่อคุณลบ field จริง ๆ อย่าให้ number (หรือ name) ของตัวเองถูก reuse — field ในอนาคตที่ใช้ number เดิมจะอ่านข้อมูลเก่าผิดแบบเงียบ ๆ ให้ mark เป็น reserved:

message User {
reserved 4, 7;
reserved "phone_number";
int64 id = 1;
string name = 2;
}

นี่คือ guardrail ระดับ schema ที่อยู่เบื้องหลังกฎ field number: compiler จะปฏิเสธการเอา 4, 7 หรือ phone_number ไปใช้ซ้ำ

คนพลาดการมองเห็น breaking change ตอน review buf breaking เทียบ .proto ของคุณกับ baseline (version ที่ release ล่าสุด, git ref หรือ registry) แล้วทำให้ CI fail ถ้า change ไหนจะทำลาย wire หรือ generated code:

Terminal window
buf breaking --against '.git#branch=main'

การต่อสิ่งนี้เข้า CI เปลี่ยน “ช่วยจำกฎ compatibility ทั้งหมดด้วยนะ” ให้กลายเป็นด่านอัตโนมัติ — นิสัยที่ให้ leverage สูงสุดสำหรับ schema ที่หลายทีมพึ่งพา

convention ใส่ major version ของ API ไว้ที่ไหน?
change ไหนปลอดภัย (additive) และไม่ต้องขึ้น version ใหม่?
ทำไมต้อง reserve number ของ field ที่ลบไปแล้ว?
การรัน `buf breaking` ใน CI ได้อะไร?