1:1 실시간 채팅 기능 ‐ 기술적 의사결정 - fitpassTeam/fitpass GitHub Wiki
FitPass 플랫폼에서는 **사용자(User)**와 헬스장 운영자(Gym Owner) 간의 1:1 실시간 채팅 기능이 필수적이었습니다.
기존 REST API 방식으로는 실시간성 요구를 만족할 수 없었기에 WebSocket 기반의 양방향 통신이 필요했습니다.
- 실시간 메시징
- 1:1 사용자-체육관 간 독립 채팅방
- 채팅 내역 저장 (영속성)
- 다중 사용자 접속 확장성
방식 | 실시간성 | 리소스 효율성 | 구현 복잡도 | 양방향 |
---|---|---|---|---|
HTTP Polling | ❌ 낮음 | ❌ 낮음 | ✅ 쉬움 | ✅ 가능 |
Server-Sent Events | ❌ 단방향 | |||
WebSocket | ✅ 높음 | ✅ 효율적 | ✅ 양방향 |
선택: WebSocket + STOMP 프로토콜
선택: Spring 내장 브로커(SimpleBroker)
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic", "/queue");
registry.setUserDestinationPrefix("/user");
}
}
선택 이유:
내장 브로커로 운영 복잡도 ↓
외부 의존성 없음 (Redis, RabbitMQ 불필요)
초기 서비스에는 충분한 성능
선택: 하이브리드 (공용 + 사용자 개별 전송)
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public") // 전체 사용자에게 브로드캐스트
public ChatMessageResponseDto sendMessage(@Payload ChatMessageRequestDto request) {
// DB 저장 후 개별 사용자에게 전송
String receiverDestination = "/user/" + receiverId + "/queue/messages";
messagingTemplate.convertAndSend(receiverDestination, responseDto);
return responseDto;
}
장점:
전체 채널 + 개별 사용자 동시 지원
보안 분리 (user prefix 기반)
확장 시 그룹 채팅 등 대응 가능
- 채팅방 (ChatRoom)
@Entity
public class ChatRoom {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 유저 (1:1 채팅 대상)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
// 체육관 (운영자 또는 트레이너 소속)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "gym_id", nullable = false)
private Gym gym;
}
- 메시지 (ChatMessage)
@Entity
public class ChatMessage {
@ManyToOne(fetch = FetchType.LAZY)
private ChatRoom chatRoom;
@Enumerated(EnumType.STRING)
private SenderType senderType; // USER / GYM
@Column(nullable = false)
private String content;
}
- 1:1 관계 기반 채팅방 유지 (User - Gym 쌍 기준)
- 발신자 타입 구분(SenderType)으로 프론트 렌더링 최적화
- 지연 로딩(FetchType.LAZY) 기반 성능 최적화
- 향후 확장 전략
@Configuration
@Profile("production")
public class ScalableWebSocketConfig {
// Redis 기반 멀티 노드 메시지 브로커 연동
}
-
파일 전송 기능
이미지, 문서 등 다양한 파일을 채팅 내에서 주고받을 수 있도록 기능 추가
-
읽음 확인(Read Receipt)
사용자별 메시지 읽음 상태 표시 기능 구현
-
푸시 알림 연동
- FCM (Firebase Cloud Messaging): 모바일 앱 푸시 알림
- Web Push: 브라우저 푸시 알림 지원
-
단순한 구조
- 내장 브로커(SimpleBroker) 기반으로 빠른 MVP 개발 및 운영 가능
- 복잡성 최소화로 유지보수 용이
-
표준 프로토콜(STOMP) 채택
- 다양한 클라이언트(Web, Mobile)와 호환성 보장
- Redis, RabbitMQ 등 메시지 브로커로 무중단 확장 가능
-
점진적 확장 전략
- 초기에는 단일 서버, 내장 브로커 사용
- 추후 Redis 기반 멀티 노드 환경으로 자연스러운 확장 가능