การดู : 0

12/04/2026 18:16น.

Golang The Series EP.136: WebSocket Versioning อัปเกรดระบบให้ล้ำ โดยไม่ทิ้ง User เวอร์ชันเก่า

Golang The Series EP.136: WebSocket Versioning อัปเกรดระบบให้ล้ำ โดยไม่ทิ้ง User เวอร์ชันเก่า

#Golang

#WebSocket

#Versioning

#API Design

#Backward Compatibility

#Subprotocol

ยินดีต้อนรับชาว Gopher ทุกท่านครับ! ในโลกของ REST API เราอาจจะคุ้นเคยกับการใส่ /v1/ หรือ /v2/ ไว้ใน URL แต่สำหรับ WebSocket ซึ่งเป็นการเชื่อมต่อแบบยาว (Long-lived Connection) การจัดการ Versioning นั้น "ปวดตับ" กว่าหลายเท่า เพราะมันไม่ใช่แค่เรื่องของเส้นทาง (Path) แต่เป็นเรื่องของโครงสร้างข้อความ (Payload Schema) ที่ต้องคุยกันให้รู้เรื่องตลอดการเชื่อมต่อครับ

วันนี้เราจะมาดูแผนรับมือเรื่องการจัดการเวอร์ชัน เพื่อให้ระบบของ Superdev Academy อัปเกรดได้ลื่นไหลแบบไม่มีใครต้องหลั่งน้ำตาครับ

 

1. ทำไม WebSocket Versioning ถึงท้าทายกว่าปกติ?

 

ความต่างของ WebSocket กับ REST ในเรื่อง Versioning ที่คุณต้องระวังคือ:

  • Stateful Lock: เมื่อ Client เชื่อมต่อสำเร็จแล้ว เวอร์ชันจะถูก "ล็อก" ไว้ตลอดอายุของท่อนั้น หากมีการอัปเดต Server กลางคัน คุณต้องตัดสินใจว่าจะตัดการเชื่อมต่อเพื่อบังคับอัปเดต หรือจะยอมให้รันขนานกันไป
  • Asynchronous Complexity: Server อาจจะส่ง Message เวอร์ชันใหม่ไปหา Client ที่ยังรันโค้ดเวอร์ชันเก่าอยู่ (หรือในทางกลับกัน) ทำให้ระบบอาจจะล่มเพราะ "อ่านข้อมูลไม่ไม่ออก"
  • Hidden Breaking Changes: การเปลี่ยนชื่อฟิลด์แค่ตัวเดียวใน JSON อาจทำให้ระบบ Real-time ของคุณกลายเป็น "หลุมดำ" ได้ทันที

 

2. 3 กลยุทธ์การทำ Versioning (เลือกอาวุธให้เหมาะกับงาน)

 

ท่าที่ 1: Path-based Versioning (เรียบง่ายแต่ได้ผล)

แยก Endpoint ชัดเจน เช่น ws://api.com/v1/chat และ ws://api.com/v2/chat

  • เหมาะสำหรับ: การเปลี่ยนโครงสร้างระบบครั้งใหญ่ (Major Change)
  • ข้อดี: จัดการที่ API Gateway ง่าย แยก Logic ใน Code ได้เด็ดขาด

 

ท่าที่ 2: Subprotocol Negotiation (ท่ามาตรฐาน IETF)

ใช้ Header Sec-WebSocket-Protocol ในช่วง Handshake เพื่อตกลงเวอร์ชันกัน

 

Go
// ฝั่ง Server (Go)
var upgrader = websocket.Upgrader{
    // Server ประกาศว่ารองรับทั้ง v1 และ v2
    Subprotocols: []string{"v1.json", "v2.json"},
}

func (h *Hub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    conn, _ := upgrader.Upgrade(w, r, nil)
    slog.Info("Client connected", "protocol", conn.Subprotocol())
}

 

  • ข้อดี: เป็นมาตรฐานสากล Client และ Server ตกลงเวอร์ชันกันได้ก่อนเริ่มคุย

 

ท่าที่ 3: Internal Payload Versioning (ยืดหยุ่นสูงสุด)

ฝังฟิลด์ v หรือ version ลงไปในทุก Message

  • เหมาะสำหรับ: ระบบที่ฟีเจอร์อัปเดตบ่อย (Continuous Delivery)

 

3. Implementation: การเขียน Go ให้รองรับ Backward Compatibility

 

เทคนิคที่ผมแนะนำคือการใช้ json.RawMessage เพื่อหลีกเลี่ยงการทำ Double Unmarshal ซึ่งจะช่วยประหยัด CPU ได้มหาศาลครับ

ตัวอย่าง: Elegant Message Router

 

Go
type Envelope struct {
    Version int             `json:"v"`
    Type    string          `json:"t"`
    Payload json.RawMessage `json:"p"` // เก็บข้อมูลดิบไว้ก่อน
}

func handleMessage(data []byte) {
    var env Envelope
    if err := json.Unmarshal(data, &env); err != nil {
        return
    }

    switch env.Version {
    case 2:
        processV2(env.Type, env.Payload)
    default:
        // Default เป็น V1 เสมอเพื่อให้ Backward Compatible
        processV1(env.Type, env.Payload) 
    }
}

 

Golden Rule: การทำ Backward Compatibility ที่ดีคือ "Add, Don't Rename" หากต้องการเปลี่ยนชื่อฟิลด์ ให้เพิ่มฟิลด์ใหม่เข้าไปและคงฟิลด์เก่าไว้ (Mark as Deprecated) จนกว่า User จะย้ายออกไปหมดครับ

 

4. กลยุทธ์ "Sunsetting" (การบอกลาอย่างเป็นระบบ)

 

เราไม่สามารถแบก Technical Debt ไว้ได้ตลอดไป แผนการปลดระวางเวอร์ชันเก่าควรเป็นดังนี้:

  1. Announcement: ส่ง System Message แจ้ง Client เวอร์ชันเก่าทุกครั้งที่เชื่อมต่อ
  2. Monitoring (EP.128): เช็ค Grafana ว่าปริมาณ Traffic ของ V1 เหลือเท่าไหร่ (ถ้าต่ำกว่า 1-5% คือจุดที่ปลอดภัย)
  3. Brownout Tests: ลองปิดเวอร์ชันเก่าเป็นช่วงเวลาสั้นๆ (เช่น 15 นาที) เพื่อดูเสียงตอบรับก่อนปิดจริง

 

5. Compatibility ในโลก Microservices

 

หากคุณใช้ Redis Pub/Sub (จาก EP.130) เป็น Backplane อย่าลืมว่า Message ที่วิ่งอยู่ใน Redis คือ "ภาษากลาง"

  • Forward Compatibility: Service เวอร์ชันเก่าควรถูกเขียนให้ "มองข้าม" ฟิลด์ใหม่ๆ ที่มันไม่รู้จักได้โดยไม่พ่น Error (ใช้สถาปัตยกรรมแบบ Permissive Parser)
  • Schema Registry: ในระบบขนาดใหญ่ การใช้ Protocol Buffers (Protobuf) จะช่วยบังคับเรื่อง Backward Compatibility ได้ดีกว่า JSON มากครับ

 


 

สรุป

 

การทำ Versioning & Backward Compatibility คือการแสดงความเคารพต่อผู้ใช้งานครับ ระบบที่ล้ำสมัยต้องมาพร้อมกับความใส่ใจใน User ที่อาจจะยังไม่พร้อมอัปเกรด การวางโครงสร้างเรื่องเวอร์ชันตั้งแต่วันแรก จะทำให้คุณนอนหลับฝันดีแม้ในวันที่ต้อง Deploy ฟีเจอร์ใหญ่ระดับประเทศครับ

ในตอนหน้า (EP.137): เราจะเข้าสู่เนื้อหาที่เข้มข้นที่สุดในซีรีส์ Enterprise WebSocket Security Best Practices เจาะลึกการป้องกันระบบระดับองค์กรจากการโจมตีทุกรูปแบบ (DoS, Injection, Hijacking) ห้ามพลาดครับ!