Design (LLD) a real-time chat system with support for millions of concurrent users - Machine Coding

Design (LLD) a real-time chat system with support for millions of concurrent users - Machine Coding

Table of contents

No heading

No headings in the article.

Features Required:

  1. User Registration and Authentication: Users should be able to create accounts, log in, and authenticate themselves to access the chat system.

  2. Real-Time Messaging: Users should be able to send and receive messages in real-time, enabling instant communication.

  3. Group Chat: The system should support group chats, allowing multiple users to participate in a single conversation.

  4. Private Messaging: Users should be able to send private messages to specific users, ensuring privacy and one-on-one communication.

  5. Presence and Status: The system should track the presence and online status of users, indicating whether they are currently active or offline.

  6. Message History: Users should be able to view and retrieve their message history, including group chat conversations and private messages.

  7. Notifications: The system should provide notifications to users for new messages, chat invitations, or important system updates.

  8. Scalability and Performance: The system should be designed to handle millions of concurrent users, ensuring high performance and responsiveness.

Design Patterns Involved or Used:

  1. Model-View-Controller (MVC) Pattern: The MVC pattern can be used to separate the application into three components: the model (data and business logic), the view (user interface), and the controller (handles user interactions and manages the flow of data).

  2. Observer Pattern: The Observer pattern can be used to notify users about new messages, chat invitations, or presence updates.

  3. Factory Pattern: The Factory pattern can be used to create different types of objects, such as user accounts, chat rooms, or messages, based on user requests.

  4. Singleton Pattern: The Singleton pattern can be used to ensure that only one instance of certain classes, such as the user authentication manager or the message dispatcher, is created and shared across the system.

  5. Proxy Pattern: The Proxy pattern can be used to handle communication between clients and the actual chat server, providing a level of indirection and encapsulation for network operations.

  6. Command Pattern: The Command pattern can be used to encapsulate and decouple actions, such as sending messages or joining chat rooms, from the specific objects or components that perform those actions.

  7. Publish-Subscribe Pattern: The Publish-Subscribe pattern can be used to implement the messaging system, where users subscribe to specific topics or channels to receive messages, and publishers send messages to those topics.

  8. Load Balancing Pattern: The Load Balancing pattern can be used to distribute incoming user requests across multiple chat servers to handle the high load of concurrent users.

Code: Detailed Implementation of Classes Based on Each Design Pattern Mentioned Above

// User class
class User {
    private String userId;
    private String username;
    private String password;
    // Other attributes and methods

    public User(String userId, String username, String password) {
        this.userId = userId;
        this.username = username;
        this.password = password;
    }

    // Getters and setters
}

// Message class
class Message {
    private String messageId;
    private User sender;
    private String content;
    private LocalDateTime timestamp;
    // Other attributes and methods

    public Message(String messageId, User sender, String content) {
        this.messageId = messageId;
        this.sender = sender;
        this.content = content;
        this.timestamp = LocalDateTime.now();
    }

    // Getters and setters
}

// ChatRoom class
class ChatRoom {
    private String roomId;
    private List<User> participants;
    private List<Message> messages;
    // Other attributes and methods

    public ChatRoom(String roomId) {
        this.roomId = roomId;
        this.participants = new ArrayList<>();
        this.messages = new ArrayList<>();
    }

    public void addParticipant(User user) {
        participants.add(user);
    }

    public void removeParticipant(User user) {
        participants.remove(user);
    }

    public void sendMessage(Message message) {
        messages.add(message);
    }

    // Other chat room operations
}

// PresenceObserver interface
interface PresenceObserver {
    void onPresenceChange(User user, boolean online);
}

// PresenceManager class (Singleton)
class PresenceManager {
    private static PresenceManager instance;
    private Map<User, Boolean> presenceMap;
    private List<PresenceObserver> observers;

    private PresenceManager() {
        this.presenceMap = new HashMap<>();
        this.observers = new ArrayList<>();
    }

    public static synchronized PresenceManager getInstance() {
        if (instance == null) {
            instance = new PresenceManager();
        }
        return instance;
    }

    public void setPresence(User user, boolean online) {
        presenceMap.put(user, online);
        notifyObservers(user, online);
    }

    public void addObserver(PresenceObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(PresenceObserver observer) {
        observers.remove(observer);
    }

    private void notifyObservers(User user, boolean online) {
        for (PresenceObserver observer : observers) {
            observer.onPresenceChange(user, online);
        }
    }
}

// ChatServerProxy class (Proxy)
class ChatServerProxy {
    private ChatServer chatServer;

    public ChatServerProxy() {
        this.chatServer = new ChatServer();
    }

    public void sendMessage(Message message) {
        chatServer.sendMessage(message);
    }

    // Other proxy methods
}

// ChatServer class
class ChatServer {
    private Map<String, ChatRoom> chatRooms;
    private Map<String, List<Message>> messageHistory;

    public ChatServer() {
        this.chatRooms = new HashMap<>();
        this.messageHistory = new HashMap<>();
    }

    public void sendMessage(Message message) {
        String roomId = message.getRoomId();
        ChatRoom room = chatRooms.get(roomId);
        if (room != null) {
            room.sendMessage(message);
            storeMessageInHistory(message);
        }
    }

    private void storeMessageInHistory(Message message) {
        String roomId = message.getRoomId();
        List<Message> roomMessages = messageHistory.computeIfAbsent(roomId, k -> new ArrayList<>());
        roomMessages.add(message);
    }

    // Other server methods
}

// LoadBalancer class
class LoadBalancer {
    private List<ChatServer> chatServers;

    public LoadBalancer() {
        this.chatServers = new ArrayList<>();
    }

    public void addServer(ChatServer server) {
        chatServers.add(server);
    }

    public ChatServer getChatServer() {
        // Implement load balancing algorithm to choose a chat server
        // based on current load and availability
        return chatServers.get(0);
    }

    // Other load balancing operations
}

// Main Class
public class RealTimeChatSystem {
    public static void main(String[] args) {
        // Create users
        User user1 = new User("user1", "John", "password1");
        User user2 = new User("user2", "Alice", "password2");

        // Create chat rooms
        ChatRoom room1 = new ChatRoom("room1");
        room1.addParticipant(user1);
        room1.addParticipant(user2);

        // Create messages
        Message message1 = new Message("message1", user1, "Hello, everyone!");
        Message message2 = new Message("message2", user2, "Hi, John!");

        // Send messages via proxy
        ChatServerProxy serverProxy = new ChatServerProxy();
        serverProxy.sendMessage(message1);
        serverProxy.sendMessage(message2);

        // Create presence manager
        PresenceManager presenceManager = PresenceManager.getInstance();
        presenceManager.setPresence(user1, true);
        presenceManager.setPresence(user2, true);

        // Create presence observer
        PresenceObserver observer = new PresenceObserver() {
            @Override
            public void onPresenceChange(User user, boolean online) {
                System.out.println(user.getUsername() + " is " + (online ? "online" : "offline"));
            }
        };

        // Subscribe presence observer to presence manager
        presenceManager.addObserver(observer);

        // Create load balancer
        LoadBalancer loadBalancer = new LoadBalancer();
        ChatServer server1 = new ChatServer();
        ChatServer server2 = new ChatServer();
        loadBalancer.addServer(server1);
        loadBalancer.addServer(server2);

        // Get chat server from load balancer
        ChatServer chatServer = loadBalancer.getChatServer();
        chatServer.sendMessage(message1);
        chatServer.sendMessage(message2);
    }
}

In this code example, the User class represents a user of the chat system, the Message class represents a chat message, the ChatRoom class represents a chat room with participants and messages, and the PresenceManager class manages the presence and online status of users. The ChatServerProxy acts as a proxy between the clients and the actual ChatServer, providing a level of indirection for network operations. The LoadBalancer class handles the distribution of incoming user requests across multiple chat servers to handle the high load of concurrent users.

The code demonstrates the usage of classes based on the mentioned patterns, such as the Singleton pattern for creating a single instance of the presence manager, the Factory pattern for creating different types of objects, such as user accounts, chat rooms, or messages, based on user requests, the Observer pattern for notifying users about presence changes, the Proxy pattern for handling communication between clients and the actual chat server, the Command pattern for encapsulating actions, such as sending messages or joining chat rooms, the Publish-Subscribe pattern for implementing the messaging system, and the Load Balancing pattern for distributing user requests across multiple chat servers.

Please note that this is a simplified example, and a complete implementation of a real-time chat system with support for millions of concurrent users involves more complex components, such as message routing, message persistence, user authentication and authorization, message history storage and retrieval, notification systems, integration with network protocols and infrastructure for high scalability and performance, and optimization techniques to handle the high load of concurrent connections.

Did you find this article valuable?

Support Subhahu Jain by becoming a sponsor. Any amount is appreciated!