Uso Avanzado de Namespaces¶
Los namespaces son una característica poderosa de Socket.IO que permite multiplexar múltiples canales de comunicación sobre una única conexión WebSocket.
Tabla de Contenidos¶
¿Qué son los Namespaces?¶
Los namespaces son canales de comunicación lógicamente separados que comparten la misma conexión física WebSocket. Cada namespace:
- Tiene su propio conjunto de eventos
- Maneja su propia conexión/desconexión
- Puede tener diferentes handlers
- Comparte la conexión TCP/WebSocket subyacente
Namespace por Defecto¶
El namespace por defecto es /. Todos los eventos sin namespace específico van aquí:
// Estos tres códigos son equivalentes
client.On("event", handler)
client.Of("/").On("event", handler)
defaultNs := client.Of("/")
defaultNs.On("event", handler)
Casos de Uso¶
1. Separación por Funcionalidad¶
Separa diferentes áreas de tu aplicación en namespaces distintos:
// Chat público
publicChat := client.Of("/chat")
publicChat.On("message", handlePublicMessage)
// Notificaciones del sistema
notifications := client.Of("/notifications")
notifications.On("alert", handleAlert)
// API de datos en tiempo real
dataStream := client.Of("/stream")
dataStream.On("update", handleDataUpdate)
2. Separación por Nivel de Acceso¶
Usa namespaces para diferentes niveles de permisos:
// Namespace público (todos pueden acceder)
public := client.Of("/public")
public.On("announcement", handleAnnouncement)
// Namespace de usuarios autenticados
authenticated := client.Of("/authenticated")
authenticated.OnConnect(func() {
authenticated.Emit("auth", map[string]interface{}{
"token": userToken,
})
})
authenticated.On("private-data", handlePrivateData)
// Namespace de administradores
admin := client.Of("/admin")
admin.OnConnect(func() {
admin.Emit("auth", map[string]interface{}{
"token": adminToken,
"role": "admin",
})
})
admin.On("admin-event", handleAdminEvent)
3. Salas Virtuales¶
Usa namespaces como salas virtuales separadas:
// Diferentes salas de chat
lobby := client.Of("/chat/lobby")
lobby.On("message", handleLobbyMessage)
gameRoom := client.Of("/chat/game-room-1")
gameRoom.On("message", handleGameRoomMessage)
privateRoom := client.Of("/chat/private-123")
privateRoom.On("message", handlePrivateMessage)
4. Microservicios¶
Cada microservicio puede tener su propio namespace:
// Servicio de usuarios
users := client.Of("/services/users")
users.On("user-update", handleUserUpdate)
// Servicio de pagos
payments := client.Of("/services/payments")
payments.On("payment-status", handlePaymentStatus)
// Servicio de notificaciones
notifications := client.Of("/services/notifications")
notifications.On("notify", handleNotification)
Arquitectura¶
Ciclo de Vida Independiente¶
Cada namespace tiene su propio ciclo de vida:
chat := client.Of("/chat")
api := client.Of("/api")
chat.OnConnect(func() {
fmt.Println("Chat conectado")
chat.Emit("join", "general")
})
chat.OnDisconnect(func(reason string) {
fmt.Println("Chat desconectado:", reason)
})
api.OnConnect(func() {
fmt.Println("API conectada")
api.Emit("subscribe", []string{"users", "posts"})
})
api.OnDisconnect(func(reason string) {
fmt.Println("API desconectada:", reason)
})
Eventos Aislados¶
Los eventos en un namespace no afectan otros namespaces:
chat := client.Of("/chat")
api := client.Of("/api")
// Este handler solo recibe eventos de /chat
chat.On("message", func(data ...interface{}) {
fmt.Println("Mensaje de chat:", data[0])
})
// Este handler solo recibe eventos de /api
api.On("message", func(data ...interface{}) {
fmt.Println("Mensaje de API:", data[0])
})
// Emitir en /chat no afecta /api
chat.Emit("message", "Hola desde chat")
api.Emit("message", "Hola desde API")
Patrones de Implementación¶
Patrón: Manager de Namespaces¶
Gestiona múltiples namespaces de forma centralizada:
type NamespaceManager struct {
client *socketio.Socket
namespaces map[string]*socketio.Namespace
}
func NewNamespaceManager(url string) *NamespaceManager {
return &NamespaceManager{
client: socketio.New(url),
namespaces: make(map[string]*socketio.Namespace),
}
}
func (m *NamespaceManager) Get(path string) *socketio.Namespace {
if ns, exists := m.namespaces[path]; exists {
return ns
}
ns := m.client.Of(path)
m.namespaces[path] = ns
return ns
}
func (m *NamespaceManager) Close() error {
return m.client.Close()
}
// Uso
manager := NewNamespaceManager("ws://localhost:3000")
defer manager.Close()
chat := manager.Get("/chat")
chat.On("message", handleChatMessage)
api := manager.Get("/api")
api.On("data", handleAPIData)
Patrón: Namespace Wrapper¶
Envuelve namespaces con lógica adicional:
type ChatNamespace struct {
ns *socketio.Namespace
username string
room string
}
func NewChatNamespace(client *socketio.Socket, username string) *ChatNamespace {
cn := &ChatNamespace{
ns: client.Of("/chat"),
username: username,
}
cn.setupHandlers()
return cn
}
func (cn *ChatNamespace) setupHandlers() {
cn.ns.OnConnect(func() {
fmt.Printf("%s conectado al chat\n", cn.username)
})
cn.ns.On("message", func(data ...interface{}) {
user := data[0].(string)
msg := data[1].(string)
fmt.Printf("[%s] %s: %s\n", cn.room, user, msg)
})
}
func (cn *ChatNamespace) JoinRoom(room string) {
cn.room = room
cn.ns.Emit("join-room", room)
}
func (cn *ChatNamespace) SendMessage(message string) {
cn.ns.Emit("message", cn.username, message)
}
func (cn *ChatNamespace) LeaveRoom() {
if cn.room != "" {
cn.ns.Emit("leave-room", cn.room)
cn.room = ""
}
}
// Uso
client := socketio.New("ws://localhost:3000")
chat := NewChatNamespace(client, "Juan")
chat.JoinRoom("general")
chat.SendMessage("Hola a todos!")
Patrón: Namespace con Estado¶
Mantén estado específico por namespace:
type APINamespace struct {
ns *socketio.Namespace
subscriptions []string
cache map[string]interface{}
mu sync.RWMutex
}
func NewAPINamespace(client *socketio.Socket) *APINamespace {
api := &APINamespace{
ns: client.Of("/api"),
cache: make(map[string]interface{}),
}
api.setupHandlers()
return api
}
func (a *APINamespace) setupHandlers() {
a.ns.OnConnect(func() {
// Re-subscribirse automáticamente al reconectar
if len(a.subscriptions) > 0 {
a.ns.Emit("subscribe", a.subscriptions)
}
})
a.ns.On("update", func(data ...interface{}) {
key := data[0].(string)
value := data[1]
a.mu.Lock()
a.cache[key] = value
a.mu.Unlock()
fmt.Printf("Cache actualizado: %s\n", key)
})
}
func (a *APINamespace) Subscribe(topics []string) {
a.subscriptions = append(a.subscriptions, topics...)
a.ns.Emit("subscribe", topics)
}
func (a *APINamespace) Get(key string) (interface{}, bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value, exists := a.cache[key]
return value, exists
}
// Uso
client := socketio.New("ws://localhost:3000")
api := NewAPINamespace(client)
api.Subscribe([]string{"users", "posts", "comments"})
// Usar cache
if value, ok := api.Get("users"); ok {
fmt.Println("Usuarios:", value)
}
Patrón: Dynamic Namespaces¶
Crea y destruye namespaces dinámicamente:
type DynamicNamespaceManager struct {
client *socketio.Socket
namespaces sync.Map // map[string]*socketio.Namespace
}
func NewDynamicNamespaceManager(url string) *DynamicNamespaceManager {
return &DynamicNamespaceManager{
client: socketio.New(url),
}
}
func (m *DynamicNamespaceManager) Join(path string) *socketio.Namespace {
if ns, loaded := m.namespaces.LoadOrStore(path, m.client.Of(path)); loaded {
return ns.(*socketio.Namespace)
}
ns := m.client.Of(path)
m.namespaces.Store(path, ns)
fmt.Printf("Namespace creado: %s\n", path)
return ns
}
func (m *DynamicNamespaceManager) Leave(path string) {
if ns, ok := m.namespaces.LoadAndDelete(path); ok {
fmt.Printf("Namespace eliminado: %s\n", path)
// Nota: No hay método Close() en Namespace,
// simplemente deja de usar el namespace
_ = ns
}
}
func (m *DynamicNamespaceManager) List() []string {
var paths []string
m.namespaces.Range(func(key, value interface{}) bool {
paths = append(paths, key.(string))
return true
})
return paths
}
// Uso
manager := NewDynamicNamespaceManager("ws://localhost:3000")
// Usuario se une a una sala
roomNs := manager.Join("/rooms/room-123")
roomNs.On("message", handleRoomMessage)
// Usuario sale de la sala
manager.Leave("/rooms/room-123")
// Ver salas activas
fmt.Println("Salas activas:", manager.List())
Mejores Prácticas¶
1. Organización Clara¶
Usa convenciones de nombres claras y consistentes:
// ✅ Bueno: estructura clara y jerárquica
client.Of("/chat/public")
client.Of("/chat/private")
client.Of("/api/v1")
client.Of("/api/v2")
client.Of("/admin/users")
client.Of("/admin/settings")
// ❌ Malo: nombres inconsistentes
client.Of("/chat")
client.Of("/PRIVATE-CHAT")
client.Of("/api_v1")
client.Of("/AdminPanel")
2. Manejo de Conexión por Namespace¶
Cada namespace debe manejar su propia conexión:
chat := client.Of("/chat")
api := client.Of("/api")
// ✅ Bueno: cada namespace maneja su estado
chat.OnConnect(func() {
chat.Emit("join", "general")
})
api.OnConnect(func() {
api.Emit("authenticate", token)
})
// ❌ Malo: asumir que todos se conectan al mismo tiempo
client.OnConnect(func() {
// Esto solo se ejecuta para el namespace por defecto
chat.Emit("join", "general") // Puede fallar si /chat no está conectado
})
3. Evitar Namespace Explosion¶
No crees demasiados namespaces innecesariamente:
// ✅ Bueno: namespaces por categoría
chat := client.Of("/chat")
chat.Emit("join-room", "general")
chat.Emit("join-room", "random")
// ❌ Malo: un namespace por sala
general := client.Of("/chat/general")
random := client.Of("/chat/random")
tech := client.Of("/chat/tech")
// ... 100 más
4. Documentar Namespaces¶
Documenta qué hace cada namespace:
// Namespace /chat: Mensajería en tiempo real
// Eventos: message, user-joined, user-left, typing
// Autenticación: Requerida
chat := client.Of("/chat")
// Namespace /api: Datos en tiempo real
// Eventos: update, delete, create
// Autenticación: Token JWT
api := client.Of("/api")
// Namespace /admin: Panel de administración
// Eventos: stats, user-action, system-alert
// Autenticación: Admin token
admin := client.Of("/admin")
5. Testing por Namespace¶
Prueba cada namespace independientemente:
func TestChatNamespace(t *testing.T) {
client := socketio.New("ws://localhost:3000")
defer client.Close()
chat := client.Of("/chat")
var received bool
chat.On("welcome", func(data ...interface{}) {
received = true
})
// Esperar conexión
time.Sleep(100 * time.Millisecond)
if !received {
t.Error("No se recibió evento welcome")
}
}
6. Cleanup de Namespaces¶
Limpia namespaces cuando ya no los necesites:
type RoomManager struct {
client *socketio.Socket
rooms sync.Map
}
func (rm *RoomManager) JoinRoom(roomId string) {
ns := rm.client.Of("/rooms/" + roomId)
rm.rooms.Store(roomId, ns)
ns.OnConnect(func() {
fmt.Printf("Conectado a sala %s\n", roomId)
})
}
func (rm *RoomManager) LeaveRoom(roomId string) {
if ns, ok := rm.rooms.LoadAndDelete(roomId); ok {
// El namespace dejará de recibir eventos
fmt.Printf("Saliendo de sala %s\n", roomId)
_ = ns
}
}
func (rm *RoomManager) Cleanup() {
rm.rooms.Range(func(key, value interface{}) bool {
rm.rooms.Delete(key)
return true
})
}
Ejemplo Completo: Sistema de Chat Multi-Sala¶
package main
import (
"fmt"
"sync"
socketio "github.com/arcaela/socket.io-client-go"
)
type ChatSystem struct {
client *socketio.Socket
user string
rooms map[string]*socketio.Namespace
roomsMutex sync.RWMutex
}
func NewChatSystem(url, username string) *ChatSystem {
return &ChatSystem{
client: socketio.New(url),
user: username,
rooms: make(map[string]*socketio.Namespace),
}
}
func (cs *ChatSystem) JoinRoom(roomName string) error {
cs.roomsMutex.Lock()
defer cs.roomsMutex.Unlock()
if _, exists := cs.rooms[roomName]; exists {
return fmt.Errorf("ya estás en la sala %s", roomName)
}
// Crear namespace para la sala
room := cs.client.Of("/chat/" + roomName)
// Configurar handlers
room.OnConnect(func() {
fmt.Printf("✅ Conectado a sala: %s\n", roomName)
room.Emit("join", cs.user)
})
room.OnDisconnect(func(reason string) {
fmt.Printf("❌ Desconectado de sala %s: %s\n", roomName, reason)
})
room.On("message", func(data ...interface{}) {
user := data[0].(string)
msg := data[1].(string)
fmt.Printf("[%s] %s: %s\n", roomName, user, msg)
})
room.On("user-joined", func(data ...interface{}) {
user := data[0].(string)
fmt.Printf("[%s] 👤 %s se unió\n", roomName, user)
})
room.On("user-left", func(data ...interface{}) {
user := data[0].(string)
fmt.Printf("[%s] 👋 %s salió\n", roomName, user)
})
cs.rooms[roomName] = room
return nil
}
func (cs *ChatSystem) LeaveRoom(roomName string) error {
cs.roomsMutex.Lock()
defer cs.roomsMutex.Unlock()
room, exists := cs.rooms[roomName]
if !exists {
return fmt.Errorf("no estás en la sala %s", roomName)
}
room.Emit("leave", cs.user)
delete(cs.rooms, roomName)
fmt.Printf("Saliste de la sala: %s\n", roomName)
return nil
}
func (cs *ChatSystem) SendMessage(roomName, message string) error {
cs.roomsMutex.RLock()
room, exists := cs.rooms[roomName]
cs.roomsMutex.RUnlock()
if !exists {
return fmt.Errorf("no estás en la sala %s", roomName)
}
return room.Emit("message", cs.user, message)
}
func (cs *ChatSystem) ListRooms() []string {
cs.roomsMutex.RLock()
defer cs.roomsMutex.RUnlock()
rooms := make([]string, 0, len(cs.rooms))
for name := range cs.rooms {
rooms = append(rooms, name)
}
return rooms
}
func (cs *ChatSystem) Close() error {
return cs.client.Close()
}
func main() {
chat := NewChatSystem("ws://localhost:3000", "Juan")
defer chat.Close()
// Unirse a múltiples salas
chat.JoinRoom("general")
chat.JoinRoom("tech")
chat.JoinRoom("random")
// Enviar mensajes
chat.SendMessage("general", "Hola a todos!")
chat.SendMessage("tech", "¿Alguien usa Go?")
// Ver salas activas
fmt.Println("Salas activas:", chat.ListRooms())
// Salir de una sala
chat.LeaveRoom("random")
select {}
}