High-level system design and component interactions
This article provides a comprehensive overview of the messaging system's architecture, including all major components and how they interact.
Detailed Component Interaction
Message Processing Flow
Group Message Flow
Online/Offline Status Flow
The system supports running multiple Socket.IO instances:
# Docker Compose configuration
socket_server_1:
environment:
SERVER_ID: server-1
RABBITMQ_HOST: rabbitmq
MEMCACHED_HOST: memcached
socket_server_2:
environment:
SERVER_ID: server-2
RABBITMQ_HOST: rabbitmq
MEMCACHED_HOST: memcached
Key Benefits:
// Socket server publishes to RabbitMQ
await rabbitMQ.publishEvent('messages', {
type: 'message:sent',
payload: {
messageId: msg.id,
channelId: msg.channel_id,
senderId: msg.sender_id,
content: msg.content,
},
})
// Chat worker consumes from RabbitMQ
consumer.subscribe('messages', async event => {
if (event.type === 'message:sent') {
await messageRepository.save(event.payload)
await messageRecipientRepository.createForChannel(event.payload)
}
})
This decouples the real-time layer (Socket) from the persistence layer (Worker).
// TypeORM manages database connections
const dataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: 5432,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Message, MessageRecipient, Chat, ...],
logging: true
})
// Used by Backend API
app.get('/api/chats/:chatId', async (req, res) => {
const messages = await dataSource.getRepository(Message).find({
where: { chat_id: req.params.chatId }
})
})
// Used by Chat Worker
consumer.subscribe('messages', async (event) => {
await dataSource.getRepository(Message).save(event.payload)
})