답변: REST는 채팅방 생성, 참가자 목록 조회 등 CRUD성 요청 처리에 적합하고, WebSocket은 메시지 전송/수신처럼 실시간성이 중요한 기능에 적합합니다. 두 방식을 병행하면 효율적인 자원 관리와 사용자 경험을 동시에 달성할 수 있습니다.
답변: STOMP는 pub/sub 구조와 헤더 기반 메시지 전송을 간결하게 구현할 수 있어 클라이언트-서버 간 통신 구조를 명확히 정의할 수 있습니다. 메시지의 목적, 타입 등을 분리해 관리하는 데 유용합니다.
답변: 일부 환경(레거시 브라우저, 기업 내부망 등)에서는 WebSocket이 동작하지 않을 수 있습니다. 이런 환경에서도 메시지를 수신할 수 있도록 SockJS를 통해 XHR, Long Polling 방식으로 fallback을 구성했습니다.
답변: STOMP 프로토콜 기준에 따라 /pub은 클라이언트가 서버로 메시지를 보내는 경로이고, /sub은 서버에서 클라이언트로 메시지를 전송하기 위한 구독 경로입니다. 역할을 명확히 나누면 보안, 로깅 처리도 용이해집니다.
답변: 조회와 변경의 책임을 분리해 관심사를 명확히 하고, 읽기 성능을 개선하거나 복잡한 쓰기 트랜잭션을 단순화하는 데 도움이 됩니다. 특히 실시간 처리가 많은 채팅 시스템에서 구조적인 이점을 얻었습니다.
답변: 예를 들어 ChatRoomService는 메시지 전송, 채팅방 생성 등의 Command 기능을, ChatRoomQueryService는 메시지 목록 조회 같은 Query 기능을 담당합니다. 레이어도 분리해 Controller에서도 명확하게 구분할 수 있도록 했습니다.
답변: 처음에는 Command와 Query 간에 공통 모델 사용을 피하려다 중복되는 DTO가 많아졌습니다. 이후 read model을 따로 구성해 단순화했고, Query 쿼리는 QueryDsl로 최적화했습니다.
답변: 메시지를 브로드캐스트한 뒤 DB 저장에 실패하면 사용자 화면과 서버 데이터가 불일치하게 됩니다. 이런 문제를 방지하기 위해 트랜잭션 커밋 이후에만 메시지를 브로드캐스트하도록 설계했습니다.
답변: 트랜잭션이 실제 커밋될 때 호출되므로, 트랜잭션이 없거나 이미 커밋된 상태에서는 작동하지 않습니다. 그래서 반드시 트랜잭션 내부에서 실행되도록 하고, 비동기 처리 시에는 주의해야 합니다.
답변: 채팅방과 유저가 복합적으로 유일한 참여자를 구성하므로, 복합키(ChatRoom + User)를 사용하는 것이 자연스러운 모델링이었습니다.
답변: JPA에서 복합 키를 사용할 경우, 컬렉션 내 비교나 ID 매핑에서 객체 동등성을 판단하기 위해 equals와 hashCode를 반드시 오버라이드해야 정확한 동작을 보장할 수 있습니다.
답변: 읽음 처리처럼 클라이언트 상태 기반 로직은 HTTP로 요청하고, 메시지 송수신은 WebSocket을 통해 실시간으로 처리했습니다. HTTP는 보장성 있고 인증처리가 용이하며, WebSocket은 반응속도에 유리합니다.
답변: 개인정보 보호와 UX 측면에서 자신이 보낸 메시지를 삭제할 수 있어야 했습니다. 다만 삭제 권한은 작성자 본인에게만 부여했습니다.
답변: 초기에는 단순하게 hard delete를 적용했습니다. 향후 감사 로그나 신고 기능이 추가되면 soft delete로 전환할 계획입니다.
답변: 참여자 테이블에 lastReadMessageId를 저장하고, 클라이언트가 현재 메시지 ID와 비교하여 읽음 여부를 판단합니다. 진입 시 HTTP로 조회하고, 이후는 WebSocket으로 실시간 업데이트합니다.
답변: lastReadMessageId만 업데이트하면 되기 때문에 쓰기 부하가 크지 않았고, 메시지 읽음 카운팅도 캐싱이나 배치로 분리해 처리했습니다. 다만 초대형 방에서는 성능 테스트를 거쳤습니다.
답변: 클라이언트가 WebSocket 연결 시 QueryParam 또는 Header로 JWT를 전달하고, HandshakeInterceptor에서 이를 검증해 Spring Security Context에 등록했습니다.
답변: Handshake 단계에서 JWT를 검증하고, 이후 WebSocket 컨트롤러나 메시지 핸들러에서 @AuthenticationPrincipal을 통해 유저 정보를 주입받아 처리했습니다. STOMP Message Header에도 JWT를 넣을 수 있도록 구성했습니다.
답변: DIRECT는 1:1 채팅, GROUP은 다자 채팅으로 나눴습니다. DIRECT는 방 참가자 외에는 접근 불가하며, GROUP은 초대받은 유저만 접근 가능합니다. 타입에 따라 권한 검증 로직이 다르게 적용됩니다.
답변: 채팅방, 유저, 참여자 간 관계가 복잡하고, 트랜잭션과 정합성 보장이 중요한 구조였습니다. Mongo는 JOIN이 어렵고 트랜잭션 처리도 제한적이라 RDB가 더 안정적이었습니다.
답변: 유저가 참여 중인 채팅방 조회, 메시지 + 작성자 정보 합치기 등 다중 테이블 JOIN이 필요한 경우 Mongo에서는 Aggregation Pipeline이 복잡해지고 성능도 떨어졌습니다.
답변: 메시지 저장, 읽음 처리, 브로드캐스트는 하나의 작업으로 보장돼야 하며, 중간에 실패하면 UX와 데이터 일관성 모두 문제가 발생합니다. 따라서 트랜잭션 단위의 처리 보장이 중요했습니다.
답변: 메시지 저장과 같은 단순 Insert/Query는 스키마 없이 빠르게 처리할 수 있었을 것입니다. TPS 면에서도 초기에는 유리했을 수 있습니다.
답변: 관계형 조회가 많고, 읽음/삭제 상태를 정합성 있게 관리하기 어려워졌을 것입니다. 채팅방 권한 검증이나 그룹 기반 제어에도 불리하게 작용했을 겁니다.
답변: 1:1 채팅과 그룹 채팅을 명확히 구분함으로써 권한, 메시지 정책, UI 처리 등을 분리할 수 있었습니다. 향후 비즈니스 채널, 커뮤니티 채팅 등으로 확장 시에도 구조를 유지하면서 대응할 수 있습니다.