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

ชื่อ​เดียว หรือ​หลาย​ชื่อ

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

การ​แก้​แบบ​นี้​ไม่มี error ไม่มี​คำ​เตือน​จาก protoc และ​อีก​ฝั่ง​ที่​ยัง​ไม่​ได้ deploy ก็​อ่าน​ผ่าน บท​นี้​วัด​ว่า​ฝั่ง​ที่​ยัง​ไม่​ได้ deploy อ่าน​ได้​ค่า​อะไร และ​มัน​เขียน​กลับ​ออก​มา​เป็น​อะไร

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

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

สอง​ชื่อ​ใน​ช่อง repeated ขึ้น​สาย​เป็น byte 17 ตัว และ tag 0a โผล่​สอง​ครั้ง

schema ทรง​นี้​ต่าง​จาก​ทรง​ตั้ง​ต้น​ที่​คำ​เดียว คือ​คำ​ว่า repeated หน้า​ช่อง​หมายเลข 1 หมายเลข​เท่า​เดิม ชนิด​ของ​ค่า​เท่า​เดิม

เติม repeated หน้า​ช่อง​เดิม — เฉพาะ​ส่วน message
message Order {
repeated string customer = 1;
int32 quantity = 2;
}

Go ใส่​ชื่อ​สอง​ชื่อ​คือ wira และ somchai แล้ว​ใส่ quantity เป็น 7 ผล​ที่​ได้​คือ 17 byte เขียน​เป็น hex ได้​แบบ​นี้

0a04776972610a07736f6d636861691007

แกะ​ที​ละ​กลุ่ม​ด้วย​กฎ​เดียว​กับ​บท​ที่ 1 คือ​ทุก​กลุ่ม​ขึ้น​ต้น​ด้วย tag แล้ว​ตาม​ด้วย​ค่า​ของ​มัน

byteค่าความ​หมาย
10atag ของ field หมายเลข 1 ชนิด​บอก​ความ​ยาว​นำ​หน้า
204ความ​ยาว​ของ​ค่า​แรก คือ 4 byte
3–677697261ตัว​อักษร​ของ wira
70atag ของ field หมายเลข 1 อีก​ครั้ง
807ความ​ยาว​ของ​ค่าที่​สอง คือ 7 byte
9–15736f6d63686169ตัว​อักษร​ของ somchai
1610tag ของ field หมายเลข 2 ชนิด varint
1707ค่า​คือ 7

สังเกต​ว่า​ไม่มี byte ตัว​ไหน​บอก​ว่า​รายการ​เริ่ม​ตรง​ไหน ไม่มี​ตัว​ไหน​บอก​ว่า​มี​สมาชิก​กี่​ตัว และ​ไม่มี​ตัว​ไหน​บอก​ว่า​ผู้​เขียน​ประกาศ​ช่อง​นี้​เป็น repeated

รูป​แบบ​บน​สายwire formatรูป​แบบ​ของ byte ที่ protobuf ใช้​จริง​บน​สาย · แต่ละ field คือ tag แล้ว​ตาม​ด้วย​ค่า และ​ไม่มี​ชื่อ field เดิน​ทาง​ไป​ด้วย​สัก​ตัวของ​ช่อง​เดียว​กับ​ของ​ช่อง​ที่​ถือ​สอง​ค่า​ใน​ตัวอย่าง​นี้ จึง​ต่าง​กัน​แค่​จำนวน​ครั้ง ที่​หมายเลข​นั้น​โผล่ ตัว​หมายเลข​กับ​ชนิด​บน​สาย​ไม่​ได้​เปลี่ยน​เลย

ข้อ​นี้​เป็น​ข้อ​ของ​ช่อง​ชนิด string ซึ่ง​ส่ง​ความ​ยาว​นำ​หน้า​ค่า​เสมอ เอกสาร proto3 ระบุ​ไว้​ว่า repeated ของ​ชนิด​ตัวเลข​ไม่​ได้​ขึ้น​สาย​เป็นก​ลุ่ม​แยก​แบบ​นี้ มัน​ขึ้น​เป็นก​ลุ่ม packed กลุ่ม​เดียว บท​นี้​ไม่​ได้​วัด​ทรง​นั้น ทุก​ข้อ​สรุป​ข้าง​ล่าง​จึง​เป็น​ข้อ​สรุป​ของ​ช่อง​ชนิด string

ผู้​อ่าน​ทรง​ตั้ง​ต้น​อ่าน byte ชุด​นั้น​ผ่าน ไม่มี unknown field และ​เก็บ​ค่า​สุดท้าย​ไว้

ผู้​อ่าน​ที่​ยัง​ถือ schema ทรง​ตั้ง​ต้น ประกาศ​ช่อง​หมายเลข 1 เป็น string customer = 1; ตัว​เดียว มัน​อ่าน byte 17 ตัว​ข้าง​บน แล้ว​ตอบ​แบบ​นี้

ช่อง​ที่​วัดGo ตอบJava ตอบ
parseokok
customersomchaisomchai
quantity77
จำนวน unknown fieldunknown fieldfield ที่​ผู้​อ่าน​ไม่รู้​จัก เพราะ schema ของ​ตัว​เอง​ไม่มี​หมายเลข​นั้น · ผู้​อ่าน​เก็บ byte ดิบ​ไว้​เฉยๆ แล้ว​ส่ง​คืน​ตอน serialize กลับ00

สอง​ภาษา​ตอบ​ตรง​กัน​ทุก​ช่อง ไม่มี exception ไม่มี​คำ​เตือน และ​ไม่มี​หมายเลข​ไหน​ตกค้าง​เป็น unknown field ให้​ใคร​สงสัย​ภาย​หลัง

ค่าที่​เหลือ​อยู่​ใน​ช่อง​คือ somchai ซึ่ง​เป็น​ค่าสุดท้ายบน​สาย ไม่ใช่ wira ซึ่ง​เป็น​ค่า​แรก กฎ​ข้อ​นี้​เป็น​กฎ​ของ protobuf เอง เอกสาร encoding เรียก​มัน​ว่า last one wins และ​เอกสาร proto3 ระบุ​ไว้​ใน​หัวข้อ​เรื่อง​การ​แก้ schema ว่า​ผู้​อ่าน​ที่​ประกาศ​ช่อง​เดียว จะ​รับ​ค่า​สุดท้าย​เมื่อ​ช่อง​นั้น​เป็น​ชนิด​พื้น​ฐาน

flowchart TD
  W["byte 17 ตัวจากผู้เขียน repeated"] --> G1["กลุ่มแรก tag 0a ค่า wira"]
  W --> G2["กลุ่มที่สอง tag 0a ค่า somchai"]
  G1 --> S["ช่อง customer ช่องเดียวของผู้อ่าน"]
  G2 --> S
  S --> V["ค่าที่เหลืออยู่คือ somchai"]
  V --> O["เขียนกลับได้ 11 byte"]

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

สั่ง​ให้​ผู้​อ่าน​ตัว​นั้น serialize กลับ ได้ byte 11 ตัว และ​ไม่มี wira อยู่​ใน​นั้น

คำถาม​ที่​สำคัญ​กว่า “ผู้​อ่าน​เห็น​ค่า​อะไร” คือ “ผู้​อ่าน​ส่ง​อะไร​ต่อ​ไป” เพราะ​บริการ​ส่วน​มาก อ่าน message มา​แล้ว​เขียน​กลับ​ออก​ไป​ให้​บริการ​ถัด​ไป

ให้​ผู้​อ่าน​ทรง​ตั้ง​ต้น​เขียน​สิ่ง​ที่​มัน​เพิ่ง​อ่าน​กลับ​ออก​มา สอง​ภาษา​ได้​ผล​ตรง​กัน

ทิศทางbytehex
ผู้​เขียน repeated เขียน​ออก​มา170a04776972610a07736f6d636861691007
ผู้​อ่าน​ช่อง​เดียว​เขียน​กลับ110a07736f6d636861691007

byte ที่​หาย​ไป​หก​ตัว​คือ​กลุ่ม​แรก​ทั้ง​กลุ่ม ทั้ง tag ทั้ง​ความ​ยาว และ​ทั้ง​สี่​ตัว​อักษร​ของ wira การ​เดิน​ทาง​ไป​กลับround tripการ​อ่าน byte เข้า​มา​แล้ว serialize กลับ​ออก​ไป · เท่า​เดิม​ทุก byte ใน​เกือบ​ทุก​งาน ยกเว้น​สอง​งาน​ที่​เหลือ​สิบ​สอง byte และ​สิบ​เอ็ด byteรอบ​นี้​จึง​ไม่​คืน​ของ​เท่า​เดิม

ตรง​นี้​คือ​จุด​ที่​ต้อง​แยก​ให้​ชัด wira ไม่​ได้​ถูก​ซ่อน​ไว้​แล้ว​รอ​เขียน​กลับ มัน​ไม่มี​ที่​อยู่​ใน message ของ​ผู้​อ่าน​ตั้งแต่​ตอน parse จบ ของ​ที่​ไม่มี​ที่​อยู่ ก็​ไม่มี​อะไร​ให้​เขียน​กลับ

บริการ​ถัด​ไป​ที่​รับ byte 11 ตัว​นี้ ไม่มี​ทาง​รู้​เลย​ว่า​เคย​มีชื่อ​สอง​ชื่อ เพราะ​สิ่ง​ที่​มัน​ได้​รับ เหมือน​กับ message ที่​มีชื่อ​เดียว​มา​ตั้งแต่​ต้นทาง​ทุก​ตัว​อักษร

สาม​งาน​อ่าน​ของ​สอง​บท​นั้น​คืน byte เท่า​เดิม ทั้ง​ที่​จำนวน unknown field ไม่​เท่า​กัน​ทุก​งาน

บท​ที่ 3 กับ​บท​ที่ 4 วัด​งาน​อ่าน​สี่​งาน​ที่ parse ผ่าน​แล้ว​เขียน​กลับ​ได้ byte เท่า​เดิม สาม​งาน​ใน​นั้น​อยู่​ใน​ตาราง​ข้าง​ล่าง สอง​งาน​แรก​ผู้​อ่าน​ไม่มี​ช่อง​รองรับ​ของ​ที่​เจอ ส่วน​งาน​ที่​สาม​มี​ช่อง​รองรับ​พอดี

กรณีunknownเขียน​กลับ
ผู้​อ่าน v1 เจอ field หมายเลข 3 ที่​เพิ่ม​มา​ใหม่ · บท​ที่ 31เท่า​เดิม
ผู้​อ่าน​ที่​เอา​หมายเลข 2 ไป​ใช้​กับ wire type อื่น · บท​ที่ 41เท่า​เดิม
ผู้​อ่าน​ที่​เอา​หมายเลข 2 ไป​ใช้​กับ int32 ตัว​อื่น · บท​ที่ 40เท่า​เดิม
ผู้​อ่าน​ช่อง​เดียว​เจอ​ชื่อ​สอง​ชื่อ · บท​นี้0ไม่​เท่า

unknown field คือ​กลไก​ที่​แบก​ของ​ข้าม​ผู้​อ่าน​รุ่น​เก่า​ไป​ให้​ปลาย​ทาง และ​กลไก​นั้น​ทำงาน​ก็​ต่อ​เมื่อ ผู้​อ่านไม่รู้​จักหมายเลข​ที่​เจอ สอง​แถว​แรก​จึง​คืน byte ครบ​ด้วย​กลไก​นี้

แถว​ที่​สาม​คืน byte ครบ​โดย​ไม่​ได้​ใช้​กลไก​นั้น​เลย ผู้​อ่าน​รู้จัก​หมายเลข 2 มี​ช่อง​รองรับ และ​รับ​ค่า​เข้า​ช่อง​ใหม่​ทั้ง​ค่า unknown จึง​เป็น 0 · เลข 0 ใน​ช่อง​นั้น​ไม่​ได้​แปล​ว่า​ของ​หาย

ความ​ต่าง​ของ​แถว​สุดท้าย​อยู่​ที่​จำนวน​กลุ่ม​บน​สาย เทียบ​กับ​จำนวน​ค่าที่​ช่อง​ของ​ผู้​อ่าน​เก็บ​ได้ สอง​กลุ่ม​ถือ​หมายเลข 1 เข้า​มา แต่​ช่อง​ของ​ผู้​อ่าน​เก็บ​ได้​ค่า​เดียว ค่า​แรก​จึง​ไม่มี​ที่​อยู่

นี่​คือ​รูป​ที่​อันตราย​กว่า​ของ​บท​ที่ 3 และ​บท​ที่ 4 หนึ่ง​ขั้น เพราะ​สาม​งาน​นั้น​ยัง​เหลือ​ของ อยู่​ใน message ทุก​งาน ส่วน​บท​นี้​ของ​หาย​ไป​โดยที่​ทุก​ด่าน​ฝั่ง​ผู้​อ่าน​เขียว​หมด

ผู้​อ่าน​ที่​ประกาศ repeated อ่าน byte ของ​ทรง​ตั้ง​ต้น​ได้​หนึ่ง​ค่า และ​เขียน​กลับ​ได้​เท่า​เดิม

สลับ​สอง​ฝั่ง ให้​ผู้​เขียน​ถือ​ทรง​ตั้ง​ต้น​ที่​มีชื่อ​เดียว แล้ว​ให้​ผู้​อ่าน​ถือ​ทรง repeated

ช่อง​ที่​วัด​ใน​ทาง​กลับGo ตอบJava ตอบ
parseokok
customerwirawira
quantity77
จำนวน unknown field00
เขียน​กลับ0a047769726110070a04776972611007

byte ที่​เขียน​กลับ​เท่ากับ 8 byte ของ​ทรง​ตั้ง​ต้น​ทุก​ตัว​อักษร ผู้​อ่าน​ได้​สมาชิก​หนึ่ง​ตัว​ลง​ใน​รายการ ของ​มัน แล้ว​เขียน​กลับ​ออก​มา​เป็นก​ลุ่ม​เดียว​เหมือน​ที่​รับ​มา

ทิศ​นี้​จึง​เป็นความ​เข้า​กัน​ได้​กับ​ของ​เดิมbackward compatibilityผู้​อ่าน​รุ่น​ใหม่​อ่าน byte ที่​ผู้​เขียน​รุ่น​เก่า​ส่ง​มา​ได้ · วัด​แล้ว​ว่า​ผู้​อ่าน v2 อ่าน​ของ v1 ได้​ค่า​ครบ และ​ไม่มี unknown สัก​หมายเลขที่แท้​จริง ผู้​อ่าน​รุ่น​ใหม่​ที่​ประกาศ repeated อ่าน​ของ​ที่​ผู้​เขียน​รุ่น​เก่า​ส่ง​มา​ได้​ครบ และ​ไม่​ทำ​ของ​หาย​ระหว่าง​ทาง

ทั้ง​สอง​ทิศ​ใน​บท​นี้​วัด​กับ​ช่อง​ชนิด string ช่อง​เดียว เอกสาร proto3 ระบุ​ความ​เข้า​กัน​ได้​ระหว่าง ช่อง​เดียว​กับ repeated ไว้​กับ string bytes และ message เท่านั้น และ​กัน​ชนิด​ตัวเลข ออก​จาก​ข้อ​นี้ เพราะ repeated ของ​ชนิด​ตัวเลข​ขึ้น​สาย​เป็นก​ลุ่ม packed

การ​แก้​ครั้ง​เดียว​ปลอดภัย​ด้าน​หนึ่ง​และ​ทำ​ของ​หาย​อีก​ด้าน​หนึ่ง ลำดับ deploy จึง​เป็น​สิ่ง​ที่​ต้อง​เลือก

สรุป​สอง​ทิศ​ที่​วัด​ได้​ใน​บท​นี้​เป็น​รายการ​เดียว

  • ผู้​อ่าน​ที่​ประกาศ repeated อ่าน​ของ​ช่อง​เดียว ได้​ค่า​ครบ และ​เขียน​กลับ​ได้ 8 byte เท่า​เดิม
  • ผู้​อ่าน​ที่​ประกาศ​ช่อง​เดียว อ่าน​ของ repeated ที่​มี​สอง​ชื่อ ได้​ค่า​สุดท้าย​ค่า​เดียว
  • ผู้​อ่าน​ตัว​หลัง​เขียน​กลับ​ได้ 11 byte จาก 17 byte ที่​รับ​มา และ​ไม่มี​อะไร​ฟ้อง

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

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

ทุกงาน​อ่านใน​หกบท​ที่​ผ่าน​มา​ถูก​วัด​สอง​รอบ ทั้ง Go และ Java ส่วน​ฝั่ง​เขียน​เป็น Go ตัว​เดียว บท​ที่ 7 วาง​งาน​อ่าน​ทั้ง​ชุด​ลง​ใน​ตาราง​เดียว แล้ว​ตอบ​ว่าการ​ยืนยัน​ด้วย​สอง​ภาษา​ซื้อ​อะไร​กลับ​มา​บ้าง


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้
  • Protocol Buffers — Encoding (เข้า​ถึง 2026-09-04) — ที่มา​ของ​กฎ last one wins และ​ของ​กฎ​ว่า​สมาชิก​ของ​ช่อง repeated ชนิด string แต่ละ​ตัว​ขึ้น​ต้น​ด้วย tag ของ​ตัว​เอง ส่วน​ชนิด​ตัวเลข​ขึ้น​เป็นก​ลุ่ม packed กลุ่ม​เดียว

  • Protocol Buffers — Language Guide (proto 3) (เข้า​ถึง 2026-09-04) — หัวข้อ​การ​แก้ schema ที่​ระบุ​ว่า​ผู้​อ่าน​ซึ่ง​ประกาศ​ช่อง​เดียว รับ​ค่า​สุดท้าย​เมื่อ​ช่อง​นั้น​เป็น​ชนิด​พื้น​ฐาน และ​ที่​จำกัด​ความ​เข้า​กัน​ได้​ระหว่าง​ช่อง​เดียว กับ repeated ไว้​กับ string bytes และ message

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

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

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

ข้อ 1 / 3

ผู้เขียนที่ประกาศ `repeated string customer = 1;` ใส่สองชื่อแล้วเขียนออกมา byte ชุดนั้นหน้าตาเป็นอย่างไร