Patterns & Alternatives
ถึงตรงนี้คุณเปิด socket ได้ ออกแบบ message ได้ รักษาการเชื่อมต่อให้อยู่ได้ และ broadcast จาก server ได้แล้ว โมดูลสุดท้ายนี้จะถอยออกมาจาก กลไก ไปสู่ รูปร่าง ของระบบ เมื่อคุณได้เห็นระบบ real-time ถูกสร้างขึ้นมาสักสองสามระบบ คุณจะสังเกตเห็น pattern ชุดเดิม ๆ ไม่กี่แบบโผล่มาครั้งแล้วครั้งเล่า — และจะสังเกตเห็นว่าบางครั้ง WebSocket ก็ไม่ใช่เครื่องมือที่ถูกต้องเสียด้วยซ้ำ บทเรียนนี้จะวางแผนที่ของดินแดนนั้นไว้ก่อนที่บทที่เหลือของโมดูลจะเติมรายละเอียดลงไป
Pattern ที่คุณมักประดิษฐ์ซ้ำอยู่เรื่อย ๆ
หัวข้อที่มีชื่อว่า “Pattern ที่คุณมักประดิษฐ์ซ้ำอยู่เรื่อย ๆ”การเชื่อมต่อแบบถาวรและสองทางคือผืนผ้าใบเปล่า อิสระแบบนั้นแหละที่ทำให้ทีมงานบังเอิญสร้างโครงสร้างเดิม ๆ ขึ้นมาใหม่ซ้ำ ๆ มีสามแบบที่ครอบคลุมฟีเจอร์ real-time ส่วนใหญ่อย่างท่วมท้น:
- Publish / subscribe. client ประกาศความสนใจใน topic บางอย่าง — ห้องแชต สัญลักษณ์หุ้น หรือ id ของเอกสาร — แล้ว server ส่งเฉพาะ event ที่ตรงกันให้เท่านั้น ไม่มีใครได้รับสายน้ำของทุกอย่างทั้งหมด แต่ละ client ได้รับเฉพาะส่วนที่ปรับมาให้ตัวเอง นี่คือกระดูกสันหลังของแอปแบบหลายผู้ใช้แทบทุกตัว
- Request / response (RPC). บางครั้ง client ต้องการ คำตอบของคำถามเฉพาะข้อนี้ จริง ๆ เมื่อส่งผ่านท่อข้อความทางเดียว ไม่มี “ตอบกลับอันนี้” มาให้ในตัว คุณจึงต้องสร้างเอง: ติด id ให้กับ request แล้ว resolve promise เมื่อ reply ที่มี id เดียวกันมาถึง
- Fire-and-forget events. client ปล่อยบางอย่างออกไป — “cursor ขยับไปที่ (x, y)”, “ผู้ใช้กำลังพิมพ์” — และไม่คาดหวัง reply ใด ๆ เลย เป็น pattern ที่ถูกที่สุด และเป็นแบบที่ WebSocket ถูกออกแบบมาเพื่อสิ่งนี้แทบจะโดยตรง
protocol ใน production ส่วนใหญ่ก็แค่สามอย่างนี้นำมาประกอบกันบนการเชื่อมต่อเดียว: subscribe เข้าห้อง, RPC เพื่อดึงประวัติของห้อง แล้ว fire-and-forget ทุกการกดแป้นพิมพ์
วิธีเลือก transport
หัวข้อที่มีชื่อว่า “วิธีเลือก transport”ก่อนจะคว้า WebSocket มาใช้โดยอัตโนมัติ ลองถามก่อนว่าข้อมูล ต้องการ อะไรจริง ๆ คำตอบตามตรงมักจะเป็น “น้อยกว่า full duplex socket” วิธีตัดสินใจอย่างรวดเร็ว:
flowchart TD start["Does the client need to send<br/>messages mid-stream too?"] start -- "No, only receive" --> sse["Server-Sent Events<br/>(simple, auto-reconnect, HTTP)"] start -- "Yes, both directions" --> freq["High-frequency or<br/>unordered media/datagrams?"] freq -- "No, ordinary messages" --> ws["WebSocket<br/>(bidirectional, ordered)"] freq -- "Yes" --> wt["WebTransport<br/>(HTTP/3, streams + datagrams)"] sse -. "no SSE / proxy strips it" .-> poll["Long-polling fallback"] ws -. "blocked by proxy" .-> poll
ประเด็นไม่ได้อยู่ที่ว่า WebSocket ไม่ดี — เป็นตัวเลือกตั้งต้นสำหรับ traffic ที่เป็นสองทางจริง ๆ ประเด็นคือ “real-time” ไม่ใช่ความต้องการแบบเดียว ticker ราคาแบบสดไหลจาก server ไปยัง client เท่านั้น ดังนั้น Server-Sent Events จึงเรียบง่ายกว่าและ reconnect ตัวเองได้ เกมแบบ multiplayer ทนต่อแพ็กเก็ตที่หลุดและสลับลำดับได้ ดังนั้น WebTransport datagram จึงเหมาะกว่า จับคู่ transport ให้เข้ากับ traffic แล้วคุณจะแบกความซับซ้อนน้อยลง
โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”โมดูลนี้ — Patterns & Alternatives — แปลงรูปร่างเหล่านั้นให้เป็นสิ่งที่คุณสร้างได้และเลือกได้:
- Patterns & Alternatives (คุณอยู่ที่นี่) — pattern ที่พบบ่อยและวิธีเลือก transport
- Pub/sub and rooms — subscribe เข้า topic รับเฉพาะสิ่งที่คุณขอ พร้อม demo ที่รันได้ในหน้านี้
- RPC over WebSocket — correlation id และ promise ที่ resolve เมื่อ reply ที่ตรงกันมาถึง พร้อม timeout
- WebSocket vs SSE vs WebTransport — เลือกระหว่าง socket สองทาง, stream ทางเดียว, fallback และ transport HTTP/3 แบบใหม่
- Building a real-time app — ผูก rooms, heartbeat, auth และ reconnect เข้าด้วยกันเป็นบริการเล็ก ๆ หนึ่งตัวบน server
wsจริง
สิ่งนี้เข้ากับภาพรวมตรงไหน
หัวข้อที่มีชื่อว่า “สิ่งนี้เข้ากับภาพรวมตรงไหน”ทุกอย่างก่อนหน้านี้เป็นชิ้นส่วนประกอบ ส่วนโมดูลนี้คือคู่มือการประกอบ ถ้าแนวคิดตรงนี้รู้สึกบางไป รากฐานอยู่ห่างออกไปแค่คลิกเดียว: client API, message design, connection lifecycle, server design และ scaling with pub/sub ทั้งหมดป้อนเข้าสู่สิ่งที่เราจะสร้างต่อไปโดยตรง