+3

Android Websocket: Nâng cấp Server với Strategy Pattern (Phần 2) - Viblo

Chào mừng các bạn quay trở lại! Ở Phần 1, chúng ta đã tạo được một ứng dụng Chat cơ bản. Tuy nhiên, nếu bạn muốn thêm các tính năng như: tạo phòng chat riêng, gửi icon, gửi ảnh, thông báo người dùng vào/ra... thì hàm handleTextMessage sẽ trở nên cực kỳ rắc rối với hàng tá câu lệnh if-else.

Tin mình đi, chiếu này từng trải rồi, từ ngày đập đi xây lại với Strategy Design Pattern mình thấy hân hoan yêu đời yêu thiên nhiên hẳn ra

Trong Phần 2 này, mình sẽ hướng dẫn các bạn "refactor" lại toàn bộ hệ thống bằng Strategy Design Pattern – một bí kíp giúp code của bạn sạch sẽ, dễ mở rộng.


1. Tại sao lại dùng Strategy Pattern?

Thay vì để một hàm duy nhất xử lý mọi loại tin nhắn, chúng ta sẽ chia nhỏ logic ra: mỗi loại tin nhắn (type) sẽ do một Class riêng biệt đảm nhận.

  • Ưu điểm: Khi muốn thêm tính năng mới, bạn chỉ cần tạo một Class mới mà không cần động vào code cũ (Tuân thủ nguyên lý Open/Closed).

2. Triển khai phía Server (Spring Boot)

Bước 1: Định nghĩa Interface xử lý tin nhắn (MessageHandler.java)

Mọi Handler sau này đều phải tuân theo "hợp đồng" này.

import org.springframework.web.socket.WebSocketSession;
import java.util.Map;

public interface MessageHandler {
    // Trả về loại tin nhắn mà handler này xử lý (ví dụ: "chat", "join_room")
    String getType();
    
    // Logic xử lý cụ thể
    void handle(WebSocketSession session, Map<String, Object> payload) throws Exception;
}

Bước 2: Tạo các Handler cụ thể (Concrete Strategies)

Ví dụ 1: Xử lý tin nhắn chat (ChatHandler.java)

import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.util.Map;

@Component
public class ChatHandler implements MessageHandler {
    @Override
    public String getType() { return "chat"; }

    @Override
    public void handle(WebSocketSession session, Map<String, Object> payload) throws Exception {
        System.out.println("Xử lý logic Chat ở đây...");
        // Broadcast tin nhắn cho những người khác (tương tự phần 1)
    }
}

Ví dụ 2: Xử lý vào phòng (JoinRoomHandler.java)

@Component
public class JoinRoomHandler implements MessageHandler {
    @Override
    public String getType() { return "join_room"; }

    @Override
    public void handle(WebSocketSession session, Map<String, Object> payload) throws Exception {
        String roomId = (String) payload.get("roomId");
        System.out.println("User " + session.getId() + " đang vào phòng: " + roomId);
        // Lưu roomId vào session attributes để quản lý
        session.getAttributes().put("ROOM_ID", roomId);
    }
}

Bước 3: Bộ điều phối trung tâm (MultiplayerWebSocketHandler.java)

Đây là nơi ma thuật xảy ra. Spring Boot sẽ tự động tìm tất cả các class có @Component và implement MessageHandler để đưa vào danh sách này.

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.*;

@Service
public class MultiplayerWebSocketHandler extends TextWebSocketHandler {
    private final Map<String, MessageHandler> handlers = new HashMap<>();
    private final ObjectMapper objectMapper = new ObjectMapper();

    // Inject tất cả MessageHandler đã tạo ở Bước 2
    public MultiplayerWebSocketHandler(List<MessageHandler> messageHandlers) {
        for (MessageHandler handler : messageHandlers) {
            handlers.put(handler.getType(), handler);
        }
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 1. Parse JSON sang Map
        Map<String, Object> payload = objectMapper.readValue(message.getPayload(), new TypeReference<>() {});
        
        // 2. Lấy "type" từ tin nhắn
        String type = (String) payload.get("type");
        
        // 3. Tìm Handler tương ứng và thực thi (Không còn if-else!)
        MessageHandler handler = handlers.get(type);
        if (handler != null) {
            handler.handle(session, payload);
        } else {
            System.out.println("Không tìm thấy handler cho loại: " + type);
        }
    }
}

3. Quản lý Room (Room Management)

Để hệ thống chuyên nghiệp hơn, chúng ta cần một RoomManager để quản lý ai đang ở phòng nào.

import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class RoomManager {
    // Map lưu trữ: RoomId -> Danh sách các Session trong phòng đó
    private final Map<String, List<WebSocketSession>> rooms = new ConcurrentHashMap<>();

    public void joinRoom(String roomId, WebSocketSession session) {
        rooms.computeIfAbsent(roomId, k -> new ArrayList<>()).add(session);
    }

    public void broadcastToRoom(String roomId, String message) {
        List<WebSocketSession> sessions = rooms.get(roomId);
        if (sessions != null) {
            for (WebSocketSession s : sessions) {
                if (s.isOpen()) {
                    try { s.sendMessage(new TextMessage(message)); } catch (Exception ignored) {}
                }
            }
        }
    }
}

4. Phía Client (Android): Cập nhật format gửi tin

Bây giờ khi gửi tin nhắn, bạn phải đính kèm thêm trường type để Server biết đường mà xử lý.

// Gửi tin nhắn chat
String chatJson = "{\"type\":\"chat\", \"content\":\"Hello World!\"}";
webSocket.send(chatJson);

// Gửi yêu cầu vào phòng
String joinJson = "{\"type\":\"join_room\", \"roomId\":\"ROOM_123\"}";
webSocket.send(joinJson);

5. Tổng kết

Bằng việc áp dụng Strategy Pattern, hệ thống của chúng ta đã:

  1. Sạch sẽ hơn: Mỗi Handler chỉ lo một việc duy nhất.
  2. Dễ mở rộng: Muốn thêm tính năng "Gửi ảnh"? Chỉ cần tạo ImageHandler và implement MessageHandler. Xong!
  3. Chuyên nghiệp: Đây là cách mà các hệ thống lớn (Game Multiplayer, App Chat nghìn người) đang vận hành.

Bonus: Các Design Pattern cho hệ thống WebSocket lớn Để hệ thống thực sự mạnh mẽ và có khả năng chịu tải cao, các project lớn các senior mình chỉ mình thường kết hợp nhiều pattern:

  • Strategy: Giúp code đọc được (Readable), tách biệt logic xử lý từng loại tin nhắn.
  • Pub/Sub: Giúp hệ thống mở rộng được nhiều server (Horizontal Scaling) thông qua Redis/Kafka.
  • Observer/Mediator: Giúp quản lý mối quan hệ giữa các User/Room phức tạp và điều phối sự kiện.
  • Decorator/Proxy: Giúp quản lý bảo mật (Authentication) và giám sát (Logging/Rate Limiting).

Hy vọng qua 2 phần của chuỗi bài viết, các bạn đã nắm vững cách xây dựng một hệ thống WebSocket "xịn xò" với Spring Boot và Android.

Chúc các bạn thành công!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí