การสร้าง Real-time Web Application คงหลีกเลี่ยงไม่ได้กับการใช้ WebSocket เพื่อให้เกิดการสื่อสารแบบสองทาง (Bi-direction Communication) เพื่อให้ฝั่ง Backend สามารถอัพเดตข้อมูลให้กับ Frontend ได้ สำหรับ Rails ก็มี ActionCable เพื่อจัดการในส่วนของ WebSocket ให้กับเรา
ที่นี้โจทย์เรามีอยู่ว่าในกรณีที่เรามีการใช้ turbo_stream_from จาก Turbo ซึ่งจะมีการสร้าง WebSocket Connection ขึ้นมาอยู่แล้ว 1 ช่อง และถ้าเราอยากจะให้เพิ่มช่องทางให้ในการส่งข้อมูลอื่นๆ ยกตัวอย่างเช่น ข้อมูล chat, ข้อมูลสถานะ เราจะสร้าง Connection ใหม่ขึ้นมา
// chat_controller.js
import { Controller } from "stimulus"
import consumer from '../channels/consumer'
export default class extends Controller {
connect() {
this.channel = consumer.subscriptions.create(
{ channel: 'ChatChannel', room: 'Funny' },
{
connected: this.cableConnected.bind(this),
disconnected: this.cableDisconnected.bind(this),
received: this.cableReceived.bind(this)
}
)
...
}# chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room].split.join.underscore}"
end
endเมื่อเรารันโปรแกรม และเปิดดูใน Inspector ในส่วนของ Network และกรองดูเฉพาะ WS ก็จะพบว่า WebSocket Connection ที่ถูกสร้างขึ้นมีตั้ง 2 ช่องด้วยกัน
- ช่องทางที่สร้างจาก Turbo
- ช่องทางที่สร้างจาก ActionCable
แน่นอนว่าเว็บแอพพลิเคชันของเราจะต้องเปิด Connection ถึง 2 ช่อง ทำให้ Server ก็ต้องแบกรับภาระมากขึ้น รวมถึงข้อจำกัดจำนวน Connection ที่ Server ตัวหนึ่งๆ จะรองรับ ทำให้ต้องเกิดการขยับขยายขึ้น เพื่อเป็นการเพิ่มประสิทธิภาพให้กับ Server อย่างน้อยเราก็ขอพยายาม Optimize Connection กันหน่อย โดยพยายามรวบ Connection ให้เหลือช่องทางเดียว เพียงเราเปลี่ยนจากการสร้าง Channel จาก ActionCable มาเป็นการสร้าง Channel จาก Turbo
import { Controller } from "stimulus"
import { subscribeTo } from '@hotwired/turbo-rails/app/javascript/turbo/cable'
export default class extends Controller {
async connect() {
await delay(200)
this.channel = subscribeTo(
{ channel: "ChatChannel", room: "Best Room" },
{
connected: this.cableConnected.bind(this),
disconnected: this.cableDisconnected.bind(this),
received: this.cableReceived.bind(this)
}
)
)
}