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

สัญญา​ที่​ตรวจ​ได้​ก่อน​ของ​จะ​ขึ้น — กับ​ดัก​เดียว​ใน​คอร์ส​นี้​ที่​เครื่อง​มือ​ปฏิเสธ​ให้

เจ็ด​บท​ที่​ผ่าน​มา​มี​อย่าง​หนึ่ง​เหมือน​กัน​หมด คือ​ไม่มี​บท​ไหน​ที่​อะไร​แดง ทุก​ทรง​ที่​แก้​แล้ว compile ผ่าน ทุก byte ที่​ส่ง​ข้าม​ฝั่ง parse ผ่าน และ​ทุก​ฝั่ง​ได้​คำ​ตอบ​กลับ​มา​โดย​ไม่​ต้อง​จับ error

บท​นี้​เปลี่ยน​เรื่อง มัน​เป็น​บท​เดียว​ของ​คอร์ส​ที่​เครื่อง​มือ​ตอบ​ว่า​ไม่ ก่อน​ที่​ของ​จะ​เดิน​ทาง​ไป​ไหน และ​ก่อน​ที่​จะ​มี code ให้​ใคร​เรียก​ด้วย​ซ้ำ

📦 orderctl — ของ​ชิ้น​เดียวกัน​กับ​สาม​คอร์ส​ภาษา​ใน​เสา​นี้

orderctl คือ​เครื่อง​มือ​ประมวล​ผล​ออ​เด​อร์​ที่​สาม​คอร์ส​ภาษา​ใน​เสา​นี้​สร้าง​ซ้ำ​ที​ละ​ภาษา repo ตัวอย่าง กำลัง​จัด​ทำ ระหว่าง​นี้​ทุก​ตัวเลข​ใน​คอร์ส​นี้​รัน​เอง​ได้ สั่ง scripts/wire-contract/up.sh เพื่อ​ดึง​เครื่อง​มือ​ที่​ตรึง version ไว้ แล้ว​ตาม​ด้วย python3 scripts/wire-contract/probe.py

protoc ออก​ด้วย​รหัส 1 และ​บอก​มา​ด้วย​ว่า​หมายเลข​ว่าง​ถัด​ไป​คือ 3

บท​ที่ 4 จบ​ด้วย​คำ​สั่ง​เดียว​ที่​ย้าย​เรื่อง หมายเลข​ซ้ำ​ไป​ให้​เครื่อง​มือ​ตอบ คือ การ​จอง​หมายเลข​ไว้​ไม่​ให้​ใคร​ใช้​ซ้ำreservedคำ​ประกาศ​ที่​กัน​หมายเลข​หรือ​ชื่อ​ไว้ ไม่​ให้​ใคร​เอา​กลับ​มา​ใช้​อีก · protoc คืน​รหัส​ออก 1 และ​บอก​หมายเลข​ว่าง​ถัด​ไป​ให้​ด้วย บท​นี้​วัด​ว่า​มัน​ตอบ​ว่า​อะไร​จริงๆ

ทรง​ข้าง​ล่าง​จอง​หมายเลข 2 กับ​ชื่อ quantity ไว้​ตาม​กฎ แล้ว​ประกาศ field ใหม่​ทับ​หมายเลข​ที่​จอง​ไป​แล้ว

❌ version ดิบ — จอง​หมายเลข 2 ไว้ แล้ว​เอา 2 กลับ​มา​ใช้
syntax = "proto3";
package order.reserved;
message Order {
string customer = 1;
reserved 2;
reserved "quantity";
int32 seats = 2;
}

สั่ง protoc -I . -o /dev/null order.proto กับ​ทรง​นี้ ผล​คือ รหัส​ออก 1 และ​ไม่มี code ออก​มา​ให้ compile ต่อ ข้อความ​ที่ protoc พิมพ์​ออก​มา​คือ

order.proto:7:12: Field "seats" uses reserved number 2. order.proto:7:12: Suggested field numbers for order.reserved.Order: 3

ข้อความ​นี้​ยาว​สอง​ประโยค และ probe ยุบ​ช่อง​ว่าง​ทั้งหมด​ให้​เหลือ​บรรทัด​เดียว​ตอน​บันทึก สิ่ง​ที่​อยู่​ใน​นั้น​จึง​มี​สอง​เรื่อง ไม่ใช่​เรื่อง​เดียว

  • ประโยค​แรก​คือ​การ​ปฏิเสธ ระบุ​ชื่อ field ที่​ผิด​และ​หมายเลข​ที่​ชน​ไว้​ตรงๆ
  • ประโยค​ที่​สอง​คือ​ข้อ​เสนอ protoc ไล่​ดู​หมายเลข​ที่​ยัง​ว่าง​ใน order.reserved.Order แล้ว​บอก​ว่า 3

ตำแหน่ง​ที่ protoc ชี้​ใน​ตัวอย่าง​นี้​คือ​บรรทัด​ที่ 7 column 12 ซึ่ง​ตรง​กับ​เลข 2 ใน​บรรทัด reserved 2; ไม่ใช่​บรรทัด​ของ seats มัน​ชี้​ไป​ที่​ข้อ​จอง​ที่​ถูก​ละเมิด ไม่ใช่​ที่​ผู้​ละเมิด

การ​จอง​ชื่อ​กับ​การ​จอง​หมายเลข​เป็น​คนละ​ข้อ ผ่าน​ข้อ​หนึ่ง​แล้ว​ยัง​ติด​อีก​ข้อ​ได้

ทรง​ที่​สอง​ทำ​ตาม​ข้อ​เสนอ​ของ protoc ทุก​อย่าง คือ​เลิก​ใช้​หมายเลข 2 แล้วไป​ใช้​หมายเลข 3 ที่ protoc บอก​เอง​ว่า​ว่าง แต่​มัน​เอา​ชื่อ​เดิม​กลับ​มา​ใช้

❌ version ดิบ — จอง​ชื่อ quantity ไว้ แล้ว​เอา​ชื่อ​นั้น​กลับ​มา​ใช้
syntax = "proto3";
package order.reservedname;
message Order {
string customer = 1;
reserved 2;
reserved "quantity";
int32 quantity = 3;
}

ผล​คือ รหัส​ออก 1 อีก​ครั้ง ข้อความ​สั้น​กว่า​เดิม​และ​ไม่มี​ข้อ​เสนอ​ต่อ​ท้าย

order.proto:9:9: Field name "quantity" is reserved.

คราว​นี้​ตำแหน่ง​ชี้​ที่​บรรทัด​ที่ 9 column 9 ซึ่ง​ตรง​กับ​ตัว​อักษร​แรก​ของ​ชื่อ quantity เอง เพราะ​ข้อ​จอง​ที่​ถูก​ละเมิด​คือ​ชื่อ และ​ชื่อ​มี​ที่​อยู่​เดียว​คือ​ตรง​นั้น

การ​จอง​ชื่อ​ไม่​ได้​ป้องกัน​เรื่อง​บน​สาย เพราะ​ชื่อ​ไม่​เคย​เดิน​ทาง​ไป​กับ byte ตาม​ที่ บท​ที่ 1 วัด​ไว้ มัน​ป้องกัน​เรื่อง​ของ​คน คือ​กัน​ไม่​ให้​ชื่อ​เดิม​กลับ​มา​หมาย​ถึง​ของ​คนละ​อย่าง​ใน​รุ่น​ถัด​ไป

กับ​ดัก​อีก​หก​อัน​ผ่าน protoc และ parse ผ่าน​ทั้ง​สอง​ฝั่ง ส่วน​กับ​ดัก​นี้​ถูก​ปฏิเสธ​ก่อน​มี code

การ​เปลี่ยน schema ตาม​เวลาschema evolutionการ​แก้ schema ที​ละ​ครั้ง​โดยที่​ของ​ซึ่ง deploy ไป​แล้ว​ยัง​ทำงาน​ต่อ​ได้ · คอร์ส​นี้​วัด​ว่าการ​แก้​แต่ละ​แบบ​ทำ​อะไร​กับ byte จริง​บ้างใน​คอร์ส​นี้​แบ่ง​ได้​เป็น​สอง​กอง กอง​ที่​เครื่อง​มือ​เห็น กับ​กอง​ที่​เครื่อง​มือ​ไม่มี​ทาง​เห็น และ​กอง​แรก​มี​สมาชิก​อยู่​รายการ​เดียว

flowchart TD
  A["แก้ schema แล้วสั่ง protoc"] --> B{"หมายเลขหรือชื่อนั้น ประกาศ reserved ไว้หรือยัง"}
  B -->|"ประกาศไว้แล้ว"| C["protoc ออกด้วยรหัส 1 และยังไม่มี code ให้ compile"]
  B -->|"ยังไม่ได้ประกาศ"| D["protoc ออกด้วยรหัส 0 และได้ code ตามปกติ"]
  D --> E["ทั้งสองฝั่ง parse ผ่าน"]
  E --> F["ค่าไปอยู่ผิดช่อง หรือหายไป โดยไม่มีอะไรแดง"]

คำ​บรรยาย​ภาพ: ทาง​เดิน​สอง​เส้น​ออก​จาก​การ​แก้ schema ครั้ง​เดียวกัน เส้น​ซ้าย​จบ​ที่​เครื่อง​มือ​ปฏิเสธ เส้น​ขวา​จบ​ที่​ค่า​ผิด​ที่​ไม่มี​ใคร​เตือน และ​เส้น​ขวา​คือ​เส้น​ที่​กับ​ดัก​อีก​หก​อัน​ของ​คอร์ส​เดิน​อยู่​ทั้งหมด

เหตุผล​ที่​เครื่อง​มือ​ไม่​เห็น​กอง​ที่​สอง คือ​มัน​ไม่มี​ของ​สอง​รุ่น​ให้​เทียบ protoc อ่าน schema ที่​วาง​อยู่ ตรง​หน้า​มัน​รุ่น​เดียว แล้ว​สร้าง โครงสร้าง​ที่​บรรยาย schemadescriptorของ​ที่​ถือ​ชื่อ หมายเลข และ​ชนิด​ของ​ทุก field ไว้​ครบ · มัน​อยู่​ข้าง code ที่ generate ออก​มา และ​ไม่​ได้​เดิน​ทาง​ไป​กับ message ของ​รุ่น​นั้น

ความ​เข้า​กัน​ได้​บน​สายwire compatibilityการ​แก้ schema ที่​ทั้ง​สอง​ฝั่ง​ยัง​อ่าน byte ของ​กัน​ได้​ตาม​ที่​ตั้งใจ · parse ผ่าน​ไม่ใช่​หลัก​ฐาน​ของ​ข้อ​นี้ เพราะ​ค่าที่​ไป​นั่ง​ผิด​ช่อง​ก็ parse ผ่าน​เหมือน​กันเป็น​คำถาม​ระหว่าง​สอง​รุ่น ไม่ใช่​คำถาม​ของ​รุ่น​ใด​รุ่น​หนึ่ง reserved ทำงาน​ได้​เพราะ​มัน​ย้าย​ความ​รู้​ของ​รุ่น​เก่า​เข้า​มา​อยู่​ใน​ตัว schema รุ่น​ใหม่

และ descriptor ตัว​นั้น​ไม่​ได้​เดิน​ทาง​ไป​กับ message ผู้​อ่าน​ปลาย​ทาง​ถือ descriptor ของ​ตัว​เอง ที่​สร้าง​จาก schema ของ​ตัว​เอง สอง​ฝั่ง​จึง​ไม่มี​โอกาส​เทียบ​กันเอง​ตอน​รัน

เจ็ด​แถว หก​แถว protoc จับ​ไม่​ได้ เหลือ​แถว​เดียว​ที่ protoc ปฏิเสธ

ทุก​ช่อง​ใน​ตาราง​นี้​มา​จาก probe ตัว​เดียวกัน หก​แถว​แรก​มี parse เป็น ok ทุก​แถว ส่วน​แถว​สุดท้าย​ไม่มี​ช่อง parse เพราะ protoc ไม่​ยอม​สร้าง code ให้​ตั้งแต่​ต้น

บทสิ่ง​ที่​แก้​ใน schemaสิ่ง​ที่​วัด​ได้protoc จับ​ได้​อะไร​ก่อน​ของ​ขึ้น
2ปล่อย quantity ไว้​ที่​ค่า​เริ่ม​ต้นทั้ง​ใบ​เหลือ 6 byte คือ 0a0477697261 และ​ผู้​อ่าน​แยก​ไม่​ออก​ว่า​ไม่​ได้​ตั้ง​ค่า หรือ​ตั้ง​เป็น​ศูนย์ไม่มี ต้อง​ประกาศ optional เอง
4เอา​หมายเลข 2 ไป​ใช้​กับ string size_nameparse ผ่าน unknown 1 หมายเลข คือ 1007 และ​ช่อง​ใหม่​ไม่มี​ค่าไม่มี เว้น​แต่​ประกาศ reserved 2; ไว้​ก่อน
4เอา​หมายเลข 2 ไป​ใช้​กับ int32 seatsparse ผ่าน unknown 0 และ seats ได้​ค่า 7 ของ​ช่อง​เดิมไม่มี เว้น​แต่​ประกาศ reserved 2; ไว้​ก่อน
5เปลี่ยน int32 เป็น uint32 แล้ว​อ่าน byte ของ −1parse ผ่าน Go ได้ 4294967295 ส่วน Java ได้ -1 และ serialize กลับ​ได้ 0a047769726110ffffffff0f ซึ่ง​ไม่​เท่า​เดิมไม่มี
5เปลี่ยน int32 เป็น sint32ผู้​อ่าน sint32 อ่าน byte ของ int32 ได้ -4 และ​ผู้​อ่าน int32 อ่าน byte ของ sint32 ได้ 14ไม่มี
6เปลี่ยน customer เป็น repeated แล้ว​เขียน​สอง​ชื่อ17 byte ผู้​อ่าน​ตัว​เดียว parse ผ่าน ได้ somchai unknown 0 และ serialize กลับ​เหลือ 11 byteไม่มี
8เอา​หมายเลข​หรือ​ชื่อ​ที่ reserved ไว้​กลับ​มา​ใช้protoc ออก​ด้วย รหัส 1 ทั้ง​สอง​แบบ และ​แบบ​หมายเลข​เสนอ​หมายเลข 3 ให้​ด้วยจับ​ได้ ตั้งแต่​ก่อน​มี code ให้ compile

ช่อง​ขวา​สุด​คือ​ช่อง​ที่​สำคัญ​ที่สุด​ของ​ตาราง​นี้ หก​แถว​แรก​เขียน​ว่า​ไม่มี เพราะ protoc เห็น schema รุ่น​เดียว ข้อมูล​ที่​จะ​ตัดสิน​ได้​จึง​ไม่​อยู่​ใน​สิ่ง​ที่​มัน​มอง​เห็น เครื่อง​มือ​ที่​เทียบ​สอง​รุ่น​เป็น​คนละ​เรื่อง และ​อยู่​ใน​รายการ​ที่​คอร์ส​นี้​ไม่​ได้​ตอบ

คอร์ส​นี้​ไม่​ได้​มี​แต่​กับ​ดัก สาม​การ​แก้​ข้าง​ล่าง​วัด​แล้ว​ว่า​ไม่​ทำให้​อีก​ฝั่ง​ได้​ค่า​ผิด

บทสิ่ง​ที่​แก้​ใน schemaสิ่ง​ที่​วัด​ได้
1เปลี่ยน​ชื่อ customer เป็น customer_namehex เท่า​เดิม​ทุก​ตัว คือ 0a04776972611007
3เพิ่ม string branch = 3; ต่อ​ท้าย18 byte ผู้​อ่าน​รุ่น​เก่า​ถือ unknown 1 หมายเลข แล้ว serialize กลับ​ได้ byte เท่า​เดิม
5ขยาย int32 เป็น int64 ซึ่ง​เป็นการ​ขยาย​ทาง​เดียวbyte เท่า​เดิม คือ 0a04776972611007 และ​อ่าน​ได้​ค่า 7 เท่า​เดิม

สาม​แถว​นี้​ไม่​เปลี่ยน​ความ​หมาย​ของ​ช่อง​ที่​ผู้​อ่าน​อีก​ฝั่ง​ถือ​อยู่ ส่วน​เจ็ด​แถว​ข้าง​บน​เปลี่ยน

และ​มัน​เปลี่ยน​คนละ​ที่​กัน บ้าง​ที่​หมายเลข บ้าง​ที่​ชนิด บ้าง​ที่​จำนวน​ค่า​ใน​ช่อง​เดียว และ​บ้าง​ที่​ผู้​อ่าน​ตอบ​ไม่​ได้​ว่า​ค่า​ถูก​ตั้ง​ไว้​หรือ​ไม่ ทั้ง​ที่​ไม่มี​ใคร​แตะ schema เลย​สัก​บรรทัด

สี่​หัวข้อ​ที่​จงใจ​ไม่​แตะ สาม​ข้อ​แรก​ด้วย​เหตุผล​เดียวกัน และ​ข้อ​สุดท้าย​ด้วย​เหตุผล​คนละ​ข้อ

  • gRPC ใน​ระดับ service คือ​ชื่อ method รหัส UNIMPLEMENTED และ streaming
  • การ map เป็น JSON ซึ่ง​มี​กฎ​ของ​ตัว​เอง​คนละ​ชุด​กับ​สิ่ง​ที่​คอร์ส​นี้​วัด
  • proto2 และ editions ซึ่ง​เปลี่ยน​กฎ​ของ field presence ไป​จาก​ที่​บท​ที่ 2 วัด​ไว้
  • buf breaking และ schema registry ซึ่ง​เป็น​เครื่อง​มือ ไม่ใช่​กฎ​ของ​สาย

สาม​ข้อ​แรก​วัด​ได้​ด้วย probe ตัว​เดิม แต่​ทุก​ข้อ​เพิ่ม​แกน​ที่​สอง​เข้าไป​ใน​ตาราง​ทุก​ใบ​ของ​คอร์ส ตาราง​ที่​เคย​มี​แถว​เป็น​ทรง​ของ schema จะ​กลาย​เป็น​แถว​คูณ​ด้วย​รูป​แบบ​ที่​สอง

ข้อ​สุดท้าย​เป็น​เครื่อง​มือ​ที่​เปลี่ยน version บ่อย​กว่า​ที่​คอร์ส​จะ​ตาม​ทัน สิ่ง​ที่​คอร์ส​นี้​ให้​ไป คือ​รายการ​ข้อ​ที่​ควร​เอา​ไป​ถาม​เครื่อง​มือ ไม่ใช่​วิธี​ตั้ง​ค่า​เครื่อง​มือ​ตัว​ใด​ตัว​หนึ่ง

สาม​คอร์ส​ใน​เสา​เดียวกัน​ที่​สร้าง orderctl ตัว​เดียวกัน ที​ละ​ภาษา

คอร์ส​นี้​อ่าน byte ที่​วิ่ง​ระหว่าง​สอง​บริการ ส่วน​สาม​คอร์ส​ข้าง​ล่าง​เขียน​ตัว​บริการ​เอง

  1. Go จาก​ศูนย์ — สร้าง orderctl ตัว​แรก​ด้วย standard library ล้วน
  2. Java จาก​ศูนย์ — สร้าง orderctl ตัว​เดิม​ใหม่​ทั้ง​ตัว​ด้วย Java 25
  3. Java 8 ถึง 25 — สำหรับ​คน​ที่​เขียน Java เป็น​แล้ว แต่​ความ​รู้​หยุด​ที่ 8 หรือ 11

ถ้า​อยาก​กลับ​ไป​ดู​ช่อง​ที่ Go กับ Java ตอบ​ไม่​เท่า​กัน​อีก​ครั้ง มัน​อยู่​ที่ บท​ที่ 7 และ​ช่อง​นั้น​มี​อยู่​ช่อง​เดียว​ใน​ทั้ง​คอร์ส


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • Protocol Buffers — Language Guide (proto 3) (เข้า​ถึง 2026-09-04) — ที่มา​ของ​กฎ reserved ทั้ง​แบบ​จอง​หมายเลข​และ​แบบ​จอง​ชื่อ

  • Protocol Buffers — Proto Best Practices (เข้า​ถึง 2026-09-04) — ที่มา​ของ​คำ​แนะนำ​ให้​จอง​หมายเลข​และ​ชื่อ​ทันที​ที่​ลบ field ทิ้ง

  • Protocol Buffers — Encoding (เข้า​ถึง 2026-09-04) — ที่มา​ของ​กฎ​ว่า​สิ่ง​ที่​เดิน​ทาง​ไป​บน​สาย​คือ​หมายเลข​กับ​ชนิด ไม่ใช่​ชื่อ

  • ตัวเลข​ทุก​ตัว​ใน​บท​นี้​มา​จาก scripts/wire-contract/probe.py ซึ่ง​สั่ง protoc ตัว​จริง​กับ​ทรง​ที่​ผิด แล้ว​บันทึก​รหัส​ออก​กับ​ข้อความ​ที่​ได้ ต้อง​สั่ง scripts/wire-contract/up.sh ก่อน​หนึ่ง​ครั้ง ซึ่ง​ดึง​เครื่อง​มือ​สาม​ตัว​ที่​ตรึง version ไว้

    • protoc 36.1
    • protoc-gen-go v1.36.12
    • protobuf-java 4.36.1

เช็กความเข้าใจ — บทที่ 8

ข้อ 1 / 3

schema ประกาศ `reserved 2;` ไว้ แล้วมีคนเพิ่ม `int32 seats = 2;` เข้าไป protoc ตอบว่าอะไร