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

Enums, Nested & oneof

enum คือ field ที่เป็นได้เพียงหนึ่งในชุด integer ที่มีชื่อกำหนดไว้ตายตัว enum สื่อ intent และกัน state ที่ไม่ถูกต้องได้ดีกว่า int32 หรือ string เปล่า ๆ มาก

enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_ACTIVE = 1;
STATUS_SUSPENDED = 2;
STATUS_CLOSED = 3;
}
message Account {
int64 id = 1;
Status status = 2;
}

มีกฎหนึ่งข้อที่ข้ามไม่ได้: value แรกของ enum ต้องเป็น 0 และควรหมายถึง “unspecified” เพราะ proto3 ไม่มี null ให้ scalar field enum ที่ไม่ถูก set จะอ่านได้เป็น zero value ถ้า 0 แปลว่า ACTIVE ทุก account ที่ลืม set status จะดูเหมือน active แบบเงียบ ๆ

flowchart TB
  unset["field ไม่ถูก set"] --> zero["อ่านได้เป็น value 0"]
  zero --> good["0 = UNSPECIFIED
→ ปลอดภัย เห็นชัดว่าไม่มีค่า"]
  zero --> bad["0 = ACTIVE
→ อันตราย ดูเหมือนตั้งใจ"]
ทำไม zero value ต้องหมายถึง 'unspecified'

convention (ที่ใช้ทั่ว API ของ Google) คือตั้งชื่อว่า <ENUM>_UNSPECIFIED = 0 ค่านี้ให้ state “ไม่มีค่า” ที่ชัดเจน และเก็บ value ที่มีความหมายไว้ให้การเลือกแบบตั้งใจ การเพิ่ม enum value ใหม่ภายหลังเป็น change ที่ปลอดภัยแบบ backward-compatible — client เก่าจะมองค่าที่ไม่รู้จักเป็น unknown เฉย ๆ

message ใส่ message อื่นข้างในได้ การ nest จัดกลุ่มข้อมูลที่เกี่ยวข้อง และให้ reuse type หรือจำกัด scope ของ type ไว้กับ message ที่เป็นเจ้าของ

message Order {
int64 id = 1;
message LineItem { // scope อยู่ใน Order (Order.LineItem)
string sku = 1;
int32 quantity = 2;
}
repeated LineItem items = 2;
Address shipping = 3; // message ระดับบนสุดที่ reuse มาใช้ที่นี่
}

nest type เมื่อ type นั้นมีความหมายเฉพาะตอนอยู่ใน parent (Order.LineItem) และเก็บไว้ระดับบนสุดเมื่อหลาย message ใช้ร่วมกัน (Address)

oneof บอกว่า “set ได้มากสุดหนึ่ง field ในกลุ่มนี้ต่อครั้ง” การ set ตัวหนึ่งจะล้างตัวอื่น และ generated code ให้วิธีเช็กชัด ๆ ว่าตัวไหนถูก set

message Notification {
int64 user_id = 1;
oneof channel {
string email = 2;
string sms_number = 3;
string push_token = 4;
}
}

ตรงนี้ notification ส่งออกผ่าน channel เดียวเป๊ะ ๆ oneof model “tagged union” ได้แม่นยำ — ดีกว่า field nullable สามตัวกับ comment ที่หวังว่าจะมีตัวเดียวถูกกรอกเยอะ และยังประหยัด memory เพราะ field เหล่านั้นใช้ storage ร่วมกัน

ทำไม value แรกของ enum ต้องเป็น 0 และหมายถึง "unspecified"?
ควร nest message ไว้ในอีก message แทนที่จะเป็นระดับบนสุดเมื่อไร?
`oneof` รับประกันอะไร?
การเพิ่ม value ใหม่ให้ enum ที่มีอยู่แล้วเป็น: