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

การออกแบบ Message

โมดูล foundations สอนคุณวิธีเปิด connection และส่ง bytes ไปมาทั้งสองทิศทาง แต่ connection ที่รับส่ง อะไรก็ได้ นั้นไม่ได้บอกคุณ เลย ว่า bytes เหล่านั้นมีความหมายอะไร โมดูลนี้ — การออกแบบ Message — คือจุดที่คุณเลิกส่ง text ดิบ ๆ แล้วเริ่มส่ง protocol

นี่คือสิ่งที่ไม่มีใครบอกคุณตอนที่คุณหยิบ WebSocket มาใช้ครั้งแรก: API มอบ send(data) และ onmessage(event) ให้คุณ และนั่นคือ ทั้งหมด connection เป็นเหมือน ท่อทึบ (opaque pipe) จะส่ง string 'hi' ก็ได้ JSON object ที่แปลงเป็น string แล้วก็ได้ หรือ binary สักก้อนก็ได้ — ท่อไม่มีความเห็นกับข้อมูลเหล่านั้นเลย ไม่มีแนวคิดสำเร็จรูปของ “chat message”, “request”, “error” หรือ “version”

ความว่างเปล่านั้นคือฟีเจอร์ ไม่ใช่ความบกพร่อง HTTP ฝัง headers, status codes, methods และการต่อรอง content ไว้ในตัว ส่วน WebSocket แทบไม่ฝังอะไรเลยเพื่อให้ คุณ ออกแบบบทสนทนาได้ตรงตามที่แอปพลิเคชันต้องการ ด้านกลับกันคือความรับผิดชอบ: message ของคุณจะมีโครงสร้างแบบไหน ก็เพราะคุณเป็นคนใส่ลงไปเอง

flowchart TB
  subgraph app["Your application"]
    a1["chat lines · presence · edits · requests"]
  end
  subgraph proto["Your message protocol — THIS module"]
    p1["type · payload · id · version · schema"]
  end
  subgraph ws["WebSocket transport"]
    w1["send(text or binary) · onmessage"]
  end
  app --> proto --> ws
  ws -- "opaque bytes" --> ws
ชั้นต่าง ๆ: transport รับส่ง bytes ส่วน protocol ของคุณรับส่งความหมาย

ชั้นตรงกลางคือชั้นที่ไม่มีค่าเริ่มต้นมาให้เลย ออกแบบชั้นนี้ให้ดีคือเส้นแบ่งระหว่าง protocol ที่ต่อยอดไปได้เป็นปี ๆ กับ protocol ที่กลายเป็นกองพันกันของ string ซึ่งต้องเขียนเงื่อนไขพิเศษเฉพาะกรณีไปเรื่อย ๆ

ห้าบทเรียน แต่ละบทเพิ่มหนึ่งแนวคิดสำคัญเข้าสู่ชุดเครื่องมือการออกแบบ message ของคุณ:

  1. การออกแบบ Message (คุณอยู่ตรงนี้) — ทำไม socket จึงว่างเปล่าและคุณคือคนใส่ความหมายลงไป
  2. Framing messages — WebSocket message นั้นแยกเป็นชิ้น ๆ อยู่แล้ว แต่คุณยังต้องมี protocol ระดับ application อยู่ดี หนึ่ง JSON object ต่อหนึ่ง message
  3. JSON vs binary — JSON ที่อ่านได้และใช้ได้ทุกที่ เทียบกับ format แบบ binary ที่กระชับอย่าง MessagePack, Protobuf และ CBOR และเมื่อไรแต่ละแบบจึงชนะ
  4. Request/response over WebSocket — socket นั้นเน้น message ไม่ใช่ request/reply คุณจึงเพิ่ม correlation id เพื่อจับคู่คำตอบกลับไปยังคำถามต้นทาง
  5. Schemas และ versioning — message envelope การ validate สิ่งที่เข้ามา และการพัฒนา protocol โดยไม่ทำให้ client รุ่นเก่าพัง

ด้านล่างคือก้าวที่เล็กที่สุดที่ขยับขึ้นมาจากการส่ง text เปล่า ๆ: การส่ง JSON object แล้ว parse ที่อีกฝั่ง โค้ดชุดนี้รันในเบราว์เซอร์ของคุณกับ echo socket ภายในหน้าที่ใช้ WebSocket API จริง — onopen, onmessage, send, close แบบเดียวกัน — จึงไม่ต้องสตาร์ท server ใด ๆ ในแอปจริง มีเพียงบรรทัดแรกเท่านั้นที่เปลี่ยนเป็น new WebSocket('wss://your-server')

สังเกตว่าทันทีที่เราส่ง object แทนที่จะเป็น string เปล่า ๆ ฝั่งรับสามารถถามคำถามที่มีความหมายได้: message นี้ ชนิดอะไร? ใคร เป็นคนส่ง? เมื่อไร?

JavaScript

socket รับส่ง text ทั้งสองทิศทางโดยไม่เคยสนใจเลยสักครั้งว่าข้างในเป็น JSON โครงสร้าง — type, from, text — มีอยู่ได้ก็เพราะ เรา ตกลงกันแล้วใส่ลงไปเอง ทุกบทเรียนที่ตามมาคือการขัดเกลาข้อตกลงนั้นให้ดียิ่งขึ้น

WebSocket transport ให้ความหมายอะไรกับข้อมูลที่คุณส่ง?
ในเดโม ทำไมฝั่งรับจึงรู้ได้ว่าได้รับ message ชนิด "chat"?
ข้อใดอธิบายบทบาทของชั้น message-protocol ได้ดีที่สุด?